| /* |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.user.cellview.client; |
| |
| import com.google.gwt.cell.client.AbstractCell; |
| import com.google.gwt.cell.client.TextCell; |
| import com.google.gwt.event.dom.client.KeyCodes; |
| import com.google.gwt.safehtml.shared.SafeHtmlBuilder; |
| import com.google.gwt.user.client.ui.RootPanel; |
| import com.google.gwt.view.client.ListDataProvider; |
| import com.google.gwt.view.client.TreeViewModel; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Tests for {@link CellTree}. |
| */ |
| public class CellTreeTest extends AbstractCellTreeTestBase { |
| |
| public CellTreeTest() { |
| super(false); |
| } |
| |
| public void testRefreshEmptyNode() { |
| // An empty data provider. |
| final ListDataProvider<String> provider = new ListDataProvider<String>(); |
| TreeViewModel model = new TreeViewModel() { |
| @Override |
| public NodeInfo<?> getNodeInfo(Object value) { |
| TextCell cell = new TextCell(); |
| return new DefaultNodeInfo<String>(provider, cell); |
| } |
| |
| @Override |
| public boolean isLeaf(Object value) { |
| return false; |
| } |
| }; |
| |
| // Create the tree. |
| CellTree tree = createAbstractCellTree(model, null); |
| tree.rootNode.listView.presenter.flush(); |
| |
| // Refresh the empty list. |
| provider.refresh(); |
| provider.flush(); |
| tree.rootNode.listView.presenter.flush(); |
| } |
| |
| /** |
| * Test that CellTree handles rendering the same content, but with a different |
| * underlying value. |
| */ |
| public void testRenderSameContent() { |
| final AbstractCell<Integer> intCell = new AbstractCell<Integer>() { |
| @Override |
| public void render(Context context, Integer value, SafeHtmlBuilder sb) { |
| sb.append(value % 10); // Render the units digit only. |
| } |
| }; |
| |
| // Create a data provider for the root node. |
| final ListDataProvider<Integer> root = new ListDataProvider<Integer>(); |
| for (int i = 0; i < 9; i++) { |
| root.getList().add(i); |
| } |
| |
| TreeViewModel model = new TreeViewModel() { |
| @Override |
| public NodeInfo<?> getNodeInfo(Object value) { |
| if (value == null) { |
| // Return the root node. |
| return new DefaultNodeInfo<Integer>(root, intCell); |
| } else { |
| // Return a child node. |
| return new DefaultNodeInfo<String>(new ListDataProvider<String>(), new TextCell()); |
| } |
| } |
| |
| @Override |
| public boolean isLeaf(Object value) { |
| return false; |
| } |
| }; |
| |
| CellTree tree = createAbstractCellTree(model, null); |
| RootPanel.get().add(tree); |
| tree.rootNode.listView.presenter.flush(); |
| |
| // Open the first child. |
| TreeNode rootNode = tree.getRootTreeNode(); |
| assertEquals(1, rootNode.getChildValue(1)); |
| TreeNode child1 = rootNode.setChildOpen(1, true); |
| assertFalse(child1.isDestroyed()); |
| assertTrue(rootNode.isChildOpen(1)); |
| |
| // Replace all values in the list. |
| List<Integer> oldData = root.getList(); |
| List<Integer> newData = new ArrayList<Integer>(); |
| for (int l : oldData) { |
| newData.add(l + 100); // renders the same as the current value. |
| } |
| root.setList(newData); |
| root.flush(); |
| tree.rootNode.listView.presenter.flush(); |
| |
| // Child1 is closed and destroyed. |
| assertFalse(rootNode.isChildOpen(1)); |
| assertTrue(child1.isDestroyed()); |
| |
| RootPanel.get().remove(tree); |
| } |
| |
| /** |
| * Test that replacing a subset of children updates both the TreeNode value |
| * and the underlying DOM correctly. |
| */ |
| public void testReplaceChildren() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode root = cellTree.getRootTreeNode(); |
| |
| // Open a couple of child nodes. |
| TreeNode a = root.setChildOpen(0, true); |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals("a", a.getValue()); |
| assertEquals("ab", a.getChildValue(1)); |
| assertEquals("b", b.getValue()); |
| assertEquals("bc", b.getChildValue(2)); |
| |
| // Replace "b" with a "new" value. |
| model.getRootDataProvider().getList().set(1, "new"); |
| model.getRootDataProvider().flush(); |
| assertFalse(a.isDestroyed()); |
| assertTrue(b.isDestroyed()); |
| TreeNode newNode = root.setChildOpen(1, true); |
| assertEquals("a", a.getValue()); |
| assertEquals("ab", a.getChildValue(1)); |
| assertEquals("new", newNode.getValue()); |
| assertEquals("newc", newNode.getChildValue(2)); |
| |
| // Check the underlying DOM values. |
| CellTreeNodeView<?> aImpl = cellTree.rootNode.getChildNode(0); |
| CellTreeNodeView<?> newNodeImpl = cellTree.rootNode.getChildNode(1); |
| assertEquals("a", aImpl.getCellParent().getInnerText()); |
| assertEquals(10, aImpl.ensureChildContainer().getChildCount()); |
| assertEquals("new", newNodeImpl.getCellParent().getInnerText()); |
| } |
| |
| public void testAriaSelectedAndExpanded() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode root = cellTree.getRootTreeNode(); |
| |
| TreeNode newNode = root.setChildOpen(1, true); |
| cellTree.rootNode.getChildNode(1).setSelected(true); |
| model.getRootDataProvider().refresh(); |
| model.getRootDataProvider().flush(); |
| root.setChildOpen(1, true); |
| CellTreeNodeView<?> newNodeImpl = cellTree.rootNode.getChildNode(1); |
| assertEquals("true", newNodeImpl.getElement().getAttribute("aria-selected")); |
| // Check aria-expanded on open |
| assertEquals("true", newNodeImpl.getElement().getAttribute("aria-expanded")); |
| |
| // Check aria-expanded on close |
| root.setChildOpen(1, false); |
| newNodeImpl = cellTree.rootNode.getChildNode(1); |
| assertEquals("false", newNodeImpl.getElement().getAttribute("aria-expanded")); |
| |
| cellTree.rootNode.getChildNode(1).setSelected(false); |
| model.getRootDataProvider().refresh(); |
| model.getRootDataProvider().flush(); |
| root.setChildOpen(1, true); |
| newNodeImpl = cellTree.rootNode.getChildNode(1); |
| assertEquals("false", newNodeImpl.getElement().getAttribute("aria-selected")); |
| } |
| |
| public void testSetDefaultNodeSize() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode root = cellTree.getRootTreeNode(); |
| assertEquals(10, root.getChildCount()); |
| |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(10, b.getChildCount()); |
| |
| // Change the default size. |
| cellTree.setDefaultNodeSize(5); |
| assertEquals(5, cellTree.getDefaultNodeSize()); |
| assertEquals(10, b.getChildCount()); |
| TreeNode d = root.setChildOpen(3, true); |
| assertEquals(5, d.getChildCount()); |
| } |
| |
| public void testAriaAttributes() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode rootNode = cellTree.getRootTreeNode(); |
| |
| // Open a child node. |
| TreeNode aNode = rootNode.setChildOpen(0, true); |
| |
| // Check role="tree" |
| assertEquals("tree", cellTree.rootNode.getElement().getAttribute("role")); |
| |
| // Check 1st level |
| CellTreeNodeView<?> aView = cellTree.rootNode.getChildNode(0); |
| CellTreeNodeView<?> bView = cellTree.rootNode.getChildNode(1); |
| |
| // Check treeitem role, level, posinset, and setsize |
| assertEquals("treeitem", aView.getElement().getAttribute("role")); |
| assertEquals("1", aView.getElement().getAttribute("aria-level")); |
| assertEquals("1", aView.getElement().getAttribute("aria-posinset")); |
| assertEquals("10", aView.getElement().getAttribute("aria-setsize")); |
| |
| assertEquals("treeitem", bView.getElement().getAttribute("role")); |
| assertEquals("1", bView.getElement().getAttribute("aria-level")); |
| assertEquals("2", bView.getElement().getAttribute("aria-posinset")); |
| assertEquals("10", bView.getElement().getAttribute("aria-setsize")); |
| |
| // Check 2nd level |
| assertTrue(aNode.getChildCount() != 0); |
| assertEquals(aNode, aView.getTreeNode()); |
| assertTrue(aView.getChildCount() != 0); |
| CellTreeNodeView<?> aViewChild = aView.getChildNode(0); |
| |
| // Check treeitem role, level, posinset, and setsize |
| assertEquals("treeitem", aViewChild.getElement().getAttribute("role")); |
| assertEquals("2", aViewChild.getElement().getAttribute("aria-level")); |
| assertEquals("1", aViewChild.getElement().getAttribute("aria-posinset")); |
| assertEquals("10", aViewChild.getElement().getAttribute("aria-setsize")); |
| |
| // Check aria-expanded |
| assertEquals("true", aView.getElement().getAttribute("aria-expanded")); |
| assertEquals("false", aViewChild.getElement().getAttribute("aria-expanded")); |
| while (!aNode.isChildLeaf(0)) { |
| aNode = aNode.setChildOpen(0, true); |
| aView = aView.getChildNode(0); |
| } |
| assertEquals(aNode, aView.getTreeNode()); |
| assertEquals("", aView.getChildNode(0).getElement().getAttribute("aria-expanded")); |
| |
| // Change default size and check aria-setsize and aria-posinset |
| cellTree.setDefaultNodeSize(5); |
| TreeNode cNode = rootNode.setChildOpen(3, true); |
| CellTreeNodeView<?> cView = cellTree.rootNode.getChildNode(3); |
| assertEquals(5, cNode.getChildCount()); |
| |
| CellTreeNodeView<?> cViewChildFirst = cView.getChildNode(0); |
| assertEquals("1", cViewChildFirst.getElement().getAttribute("aria-posinset")); |
| assertEquals("5", cViewChildFirst.getElement().getAttribute("aria-setsize")); |
| |
| CellTreeNodeView<?> cViewChildLast = cView.getChildNode(cView.getChildCount() - 1); |
| assertEquals("5", cViewChildLast.getElement().getAttribute("aria-posinset")); |
| assertEquals("5", cViewChildLast.getElement().getAttribute("aria-setsize")); |
| } |
| |
| public void testExplicitKeyboardSelection() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode root = cellTree.getRootTreeNode(); |
| |
| final String cellTreeKeyboardSelectedItemStyleName = |
| cellTree.getStyle().cellTreeKeyboardSelectedItem(); |
| |
| // Navigate through tree to level 4 nodes. |
| TreeNode l1Node = root.setChildOpen(0, true); // a |
| CellTreeNodeView<?> l1View = cellTree.rootNode.getChildNode(0); |
| TreeNode l2Node = l1Node.setChildOpen(0, true); // aa |
| CellTreeNodeView<?> l2View = l1View.getChildNode(0); |
| TreeNode l3Node = l2Node.setChildOpen(0, true); // aaa |
| CellTreeNodeView<?> l3View = l2View.getChildNode(0); |
| |
| ((CellTreeNodeView.TreeNodeImpl) l3Node).flush(); |
| CellTreeNodeView<?> l4View = l3View.getChildNode(0); |
| // l4Node is leaf, cannot get a reference via setChildOpen except via non-public nodeView |
| TreeNode l4Node = l4View.getTreeNode(); // aaaa |
| |
| // Set keyboard selected node to a subtree node, l3Node = first child of l2Node |
| cellTree.setKeyboardSelectedTreeNode(l2Node, 0, true); |
| assertEquals(l3View, cellTree.getKeyboardSelectedNode()); |
| assertEquals(l3Node, cellTree.getKeyboardSelectedTreeNode()); |
| assertTrue(CellTreeNodeView.getSelectionElement(l3View.getElement()).getClassName() |
| .indexOf(cellTreeKeyboardSelectedItemStyleName) >= 0); |
| |
| // Set keyboard selected node to a leaf node. |
| cellTree.setKeyboardSelectedTreeNode(l3Node, 0, true); |
| assertEquals(l4View, cellTree.getKeyboardSelectedNode()); |
| assertEquals(l4Node, cellTree.getKeyboardSelectedTreeNode()); |
| assertTrue(CellTreeNodeView.getSelectionElement(l4View.getElement()).getClassName() |
| .indexOf(cellTreeKeyboardSelectedItemStyleName) >= 0); |
| |
| l1Node.setChildOpen(0, false); // close l2node |
| ((CellTreeNodeView.TreeNodeImpl) l1Node).flush(); |
| |
| // Try to select a leaf node in closed subtree |
| try { |
| cellTree.setKeyboardSelectedTreeNode(l3Node, 0, true); |
| fail("should have thrown"); |
| } catch (IllegalStateException e) { |
| assertEquals("TreeNode no longer exists.", e.getMessage()); |
| } |
| |
| // Try to select a subtree node in closed subtree |
| try { |
| cellTree.setKeyboardSelectedTreeNode(l2Node, 0, true); |
| fail("should have thrown"); |
| } catch (IllegalStateException e) { |
| assertEquals("TreeNode no longer exists.", e.getMessage()); |
| } |
| |
| // Still ok to select closed subtree node |
| cellTree.setKeyboardSelectedTreeNode(l1Node, 0, true); |
| |
| // Create another tree of same structure |
| CellTree anotherTree = createAbstractCellTree(model, root.getValue()); |
| l1Node = anotherTree.getRootTreeNode().setChildOpen(0, true); // a |
| ((CellTreeNodeView.TreeNodeImpl) l1Node).flush(); |
| |
| // Now l1Node refers to a subtree node in anotherTree |
| // Select in the same cell tree is ok |
| anotherTree.setKeyboardSelectedTreeNode(l1Node, 0, true); |
| |
| // Select in a different tree will throw exception. |
| try { |
| cellTree.setKeyboardSelectedTreeNode(l1Node, 0, true); |
| fail("should have thrown"); |
| } catch (IllegalArgumentException e) { |
| assertEquals("The tree node does not belong to the tree.", e.getMessage()); |
| } |
| } |
| |
| public void testKeyboardNavigationForUpAndDownKeys() { |
| CellTree cellTree = (CellTree) tree; |
| TreeNode root = cellTree.getRootTreeNode(); |
| |
| // Open nodes: a and aj |
| TreeNode l1Node = root.setChildOpen(0, true); // a |
| assertEquals("a", l1Node.getValue()); |
| TreeNode l2Node = l1Node.setChildOpen(9, true); // aj |
| assertEquals("aj", l2Node.getValue()); |
| // To force tree structure changes (internal flush()). |
| assertEquals(10, l2Node.getChildCount()); |
| |
| // CellTree structure with node 'a' and 'aj' opened. |
| List<String> expectedNavigationPath = Arrays.asList("a", |
| "aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", |
| "aja", "ajb", "ajc", "ajd", "aje", "ajf", "ajg", "ajh", "aji", "ajj", |
| "b", "c", "d", "e", "f", "g", "h", "i", "j"); |
| |
| // Default KeyboardSelected is at "a" |
| assertEquals("a", cellTree.getKeyboardSelectedTreeNode().getValue()); |
| |
| int steps = expectedNavigationPath.size() - 1; |
| assertEquals(expectedNavigationPath, repeatKeyInTree(cellTree, KeyCodes.KEY_DOWN, steps)); |
| |
| Collections.reverse(expectedNavigationPath); |
| assertEquals(expectedNavigationPath, repeatKeyInTree(cellTree, KeyCodes.KEY_UP, steps)); |
| } |
| |
| /** |
| * Repeats a keystroke in the CellTree that contains string values. |
| * @return the nodes selected before starting and after each keystroke. |
| */ |
| private List<String> repeatKeyInTree(CellTree cellTree, int keyCode, int keyCount) { |
| List<String> values = new ArrayList<String>(); |
| values.add((String) cellTree.getKeyboardSelectedTreeNode().getValue()); |
| for (int i = 0; i < keyCount; i++) { |
| cellTree.handleKeyNavigation(keyCode); |
| values.add((String) cellTree.getKeyboardSelectedTreeNode().getValue()); |
| } |
| return values; |
| } |
| |
| @Override |
| protected <T> CellTree createAbstractCellTree(TreeViewModel model, T rootValue) { |
| return new CellTree(model, rootValue); |
| } |
| } |