| /* |
| * 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.Cell; |
| import com.google.gwt.cell.client.TextCell; |
| import com.google.gwt.event.logical.shared.CloseEvent; |
| import com.google.gwt.event.logical.shared.CloseHandler; |
| import com.google.gwt.event.logical.shared.OpenEvent; |
| import com.google.gwt.event.logical.shared.OpenHandler; |
| import com.google.gwt.junit.client.GWTTestCase; |
| import com.google.gwt.user.client.ui.RootPanel; |
| import com.google.gwt.view.client.AbstractDataProvider; |
| import com.google.gwt.view.client.ListDataProvider; |
| import com.google.gwt.view.client.TreeViewModel; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Base tests for subclasses of {@link AbstractCellTree}. |
| */ |
| public abstract class AbstractCellTreeTestBase extends GWTTestCase { |
| |
| /** |
| * The root value. |
| */ |
| private static final Object ROOT_VALUE = new Object(); |
| |
| /** |
| * A mock {@link TreeViewModel} used for testing. Each child ads a character |
| * to the parent string. The longest string in the tree is 4 characters. |
| */ |
| protected class MockTreeViewModel implements TreeViewModel { |
| |
| /** |
| * The cell used to render all nodes in the tree. |
| */ |
| private final Cell<String> cell = new TextCell(); |
| |
| /** |
| * The root data provider. |
| */ |
| private final ListDataProvider<String> rootDataProvider = |
| createDataProvider(""); |
| |
| public <T> NodeInfo<?> getNodeInfo(T value) { |
| if (value == ROOT_VALUE) { |
| return new DefaultNodeInfo<String>(rootDataProvider, cell); |
| } else if (value instanceof String) { |
| String prefix = (String) value; |
| if (prefix.length() > 3) { |
| throw new IllegalStateException( |
| "Prefix should never exceed four characters."); |
| } |
| return new DefaultNodeInfo<String>(createDataProvider(prefix), cell); |
| } |
| throw new IllegalArgumentException("Unrecognized value type"); |
| } |
| |
| public boolean isLeaf(Object value) { |
| if (value == ROOT_VALUE) { |
| return false; |
| } else if (value instanceof String) { |
| String s = (String) value; |
| if (s.length() > 4) { |
| throw new IllegalStateException( |
| "value should never exceed five characters."); |
| } |
| return ((String) value).length() == 4; |
| } |
| throw new IllegalArgumentException("Unrecognized value type"); |
| } |
| |
| public AbstractDataProvider<String> getRootDataProvider() { |
| return rootDataProvider; |
| } |
| |
| /** |
| * Create a data provider that extends the prefix by one letter. |
| * |
| * @param prefix the prefix string |
| * @return a data provider |
| */ |
| private ListDataProvider<String> createDataProvider(String prefix) { |
| ListDataProvider<String> provider = new ListDataProvider<String>(); |
| List<String> list = provider.getList(); |
| for (int i = 0; i < 10; i++) { |
| list.add(prefix + ((char) ('a' + i))); |
| } |
| provider.flush(); |
| return provider; |
| } |
| } |
| |
| /** |
| * A mock {@link CloseHandler} used for testing. |
| */ |
| private class MockCloseHandler implements CloseHandler<TreeNode> { |
| |
| private CloseEvent<TreeNode> lastEvent; |
| |
| public CloseEvent<TreeNode> getLastEventAndClear() { |
| CloseEvent<TreeNode> toRet = lastEvent; |
| lastEvent = null; |
| return toRet; |
| } |
| |
| public void onClose(CloseEvent<TreeNode> event) { |
| assertNull(lastEvent); |
| this.lastEvent = event; |
| } |
| } |
| |
| /** |
| * A mock {@link OpenHandler} used for testing. |
| */ |
| private class MockOpenHandler implements OpenHandler<TreeNode> { |
| |
| private OpenEvent<TreeNode> lastEvent; |
| |
| public OpenEvent<TreeNode> getLastEventAndClear() { |
| OpenEvent<TreeNode> toRet = lastEvent; |
| lastEvent = null; |
| return toRet; |
| } |
| |
| public void onOpen(OpenEvent<TreeNode> event) { |
| assertNull(lastEvent); |
| this.lastEvent = event; |
| } |
| } |
| |
| /** |
| * The model that backs the tree. |
| */ |
| protected MockTreeViewModel model; |
| |
| /** |
| * The current tree being tested. |
| */ |
| protected AbstractCellTree tree; |
| |
| /** |
| * If true, the tree only supports opening a single path. |
| */ |
| private final boolean singlePathOnly; |
| |
| /** |
| * Construct a new {@link AbstractCellTreeTestBase}. |
| * |
| * @param singlePathOnly true if the tree only supports a single open path |
| */ |
| public AbstractCellTreeTestBase(boolean singlePathOnly) { |
| this.singlePathOnly = singlePathOnly; |
| } |
| |
| @Override |
| public String getModuleName() { |
| return "com.google.gwt.user.cellview.CellView"; |
| } |
| |
| public void testGetRootNode() { |
| TreeNode root = tree.getRootTreeNode(); |
| assertEquals(10, root.getChildCount()); |
| assertEquals(0, root.getIndex()); |
| assertNull(root.getParent()); |
| assertEquals(ROOT_VALUE, root.getValue()); |
| testTreeNode(root, null, 0, ROOT_VALUE, false); |
| } |
| |
| public void testIsLeaf() { |
| assertFalse(tree.isLeaf(ROOT_VALUE)); |
| assertFalse(tree.isLeaf("a")); |
| assertFalse(tree.isLeaf("ab")); |
| assertFalse(tree.isLeaf("ab")); |
| assertFalse(tree.isLeaf("abc")); |
| assertTrue(tree.isLeaf("abcd")); |
| } |
| |
| /** |
| * Test that opening a sibling node works. |
| */ |
| public void testOpenSiblingNode() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Open a node. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Open a sibling node. |
| TreeNode d = root.setChildOpen(3, true); |
| if (singlePathOnly) { |
| assertFalse(root.isChildOpen(1)); |
| assertEquals(b, closeHandler.getLastEventAndClear().getTarget()); |
| } else { |
| assertTrue(root.isChildOpen(1)); |
| assertNull(closeHandler.getLastEventAndClear()); |
| } |
| assertEquals(d, openHandler.getLastEventAndClear().getTarget()); |
| assertTrue(root.isChildOpen(3)); |
| } |
| |
| /** |
| * Test a {@link TreeNode} at the leaf. We access the leaf nodes with the |
| * {@link TreeNode} that is the parent of the leaf nodes. |
| */ |
| public void testTreeNodeAtLeaf() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Walk to a parent of leaf nodes. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| TreeNode bc = b.setChildOpen(2, true); |
| assertEquals(bc, openHandler.getLastEventAndClear().getTarget()); |
| TreeNode bce = bc.setChildOpen(4, true); |
| assertEquals(bce, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Try to open the leaf. |
| assertNull(bce.setChildOpen(0, true)); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertNull(openHandler.getLastEventAndClear()); |
| |
| // Test the values associated with the node. |
| testTreeNode(bce, bc, 4, "bce", true); |
| } |
| |
| /** |
| * Test a {@link TreeNode} in the middle of the tree. |
| */ |
| public void testTreeNodeAtMiddle() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Walk to a parent of leaf nodes. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| TreeNode bc = b.setChildOpen(2, true); |
| assertEquals(bc, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Test the values associated with the node. |
| testTreeNode(bc, b, 2, "bc", false); |
| } |
| |
| /** |
| * Test that closing a branch closes all open nodes recursively. |
| */ |
| public void testTreeNodeCloseBranch() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Walk down a branch. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| TreeNode bc = b.setChildOpen(2, true); |
| assertEquals(bc, openHandler.getLastEventAndClear().getTarget()); |
| TreeNode bce = bc.setChildOpen(4, true); |
| assertEquals(bce, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Close the node at the top of the branch. |
| assertNull(root.setChildOpen(1, false)); |
| assertFalse(root.isChildOpen(1)); |
| assertTrue(b.isDestroyed()); |
| assertTrue(bc.isDestroyed()); |
| assertTrue(bce.isDestroyed()); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertEquals(b, closeHandler.getLastEventAndClear().getTarget()); |
| } |
| |
| public void testTreeNodeCloseChild() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler() { |
| @Override |
| public void onClose(CloseEvent<TreeNode> event) { |
| super.onClose(event); |
| |
| // The node should be destroyed when the close event is fired. |
| TreeNode node = event.getTarget(); |
| assertTrue(node.isDestroyed()); |
| } |
| }; |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Open a node. |
| TreeNode child = root.setChildOpen(2, true); |
| assertEquals(child, openHandler.getLastEventAndClear().getTarget()); |
| assertNull(closeHandler.getLastEventAndClear()); |
| assertTrue(root.isChildOpen(2)); |
| assertFalse(child.isDestroyed()); |
| assertEquals("c", child.getValue()); |
| assertEquals(2, child.getIndex()); |
| assertEquals(root, child.getParent()); |
| |
| // Close the child. |
| assertNull(root.setChildOpen(2, false)); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertEquals(child, closeHandler.getLastEventAndClear().getTarget()); |
| assertFalse(root.isChildOpen(2)); |
| assertFalse(root.isDestroyed()); |
| assertTrue(child.isDestroyed()); |
| } |
| |
| public void testTreeNodeCloseChildAlreadyClosed() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Open a node. |
| TreeNode child = root.setChildOpen(2, true); |
| assertEquals(child, openHandler.getLastEventAndClear().getTarget()); |
| assertNull(closeHandler.getLastEventAndClear()); |
| assertTrue(root.isChildOpen(2)); |
| assertFalse(child.isDestroyed()); |
| assertEquals("c", child.getValue()); |
| assertEquals(2, child.getIndex()); |
| assertEquals(root, child.getParent()); |
| |
| // Close the child. |
| assertNull(root.setChildOpen(2, false)); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertEquals(child, closeHandler.getLastEventAndClear().getTarget()); |
| assertFalse(root.isChildOpen(2)); |
| assertFalse(root.isDestroyed()); |
| assertTrue(child.isDestroyed()); |
| |
| // Close the child again. |
| assertNull(root.setChildOpen(2, false)); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertNull(closeHandler.getLastEventAndClear()); |
| assertFalse(root.isChildOpen(2)); |
| assertFalse(root.isDestroyed()); |
| assertTrue(child.isDestroyed()); |
| } |
| |
| /** |
| * Test that a tree node is destroyed if its associated data is lost when new |
| * data is provided to the node. |
| */ |
| public void testTreeNodeDataLost() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Get a node. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Replace the data without the old node. |
| List<String> list = new ArrayList<String>(); |
| list.add("x"); |
| list.add("y"); |
| list.add("z"); |
| model.rootDataProvider.setList(list); |
| |
| // Verify the node is destroyed. |
| assertTrue(b.isDestroyed()); |
| } |
| |
| /** |
| * Test that a tree node continues to exist when new data is pushed to the |
| * node. |
| */ |
| public void testTreeNodeDataReplaced() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Get a node. |
| TreeNode b = root.setChildOpen(1, true); |
| assertEquals(b, openHandler.getLastEventAndClear().getTarget()); |
| |
| // Replace the data and include the old node at a different location. |
| List<String> list = new ArrayList<String>(); |
| list.add("x"); |
| list.add("y"); |
| list.add("b"); |
| list.add("z"); |
| model.rootDataProvider.setList(list); |
| |
| // Verify the node still exists. |
| assertFalse(root.isChildOpen(1)); |
| assertTrue(root.isChildOpen(2)); |
| testTreeNode(b, root, 2, "b", false); |
| } |
| |
| public void testTreeNodeIsDestroyed() { |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Open a node. |
| TreeNode c = root.setChildOpen(2, true); |
| assertFalse(c.isDestroyed()); |
| |
| // Close the node. |
| assertNull(root.setChildOpen(2, false)); |
| assertFalse(root.isDestroyed()); |
| assertTrue(c.isDestroyed()); |
| |
| // Verify we can still get the value. |
| assertEquals("c", c.getValue()); |
| |
| try { |
| c.getChildCount(); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.getChildValue(0); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.getIndex(); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.getParent(); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.isChildLeaf(0); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.setChildOpen(0, true); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| try { |
| c.setChildOpen(0, true, true); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // Expected; |
| } |
| } |
| |
| /** |
| * Try to open a child that is already open. |
| */ |
| public void testTreeNodeOpenChildAlreadyOpen() { |
| MockOpenHandler openHandler = new MockOpenHandler(); |
| MockCloseHandler closeHandler = new MockCloseHandler(); |
| tree.addOpenHandler(openHandler); |
| tree.addCloseHandler(closeHandler); |
| TreeNode root = tree.getRootTreeNode(); |
| |
| // Open a node. |
| TreeNode child = root.setChildOpen(2, true); |
| assertEquals(child, openHandler.getLastEventAndClear().getTarget()); |
| assertNull(closeHandler.getLastEventAndClear()); |
| assertTrue(root.isChildOpen(2)); |
| assertFalse(child.isDestroyed()); |
| assertEquals("c", child.getValue()); |
| assertEquals(2, child.getIndex()); |
| assertEquals(root, child.getParent()); |
| |
| // Open the same node. |
| assertEquals(child, root.setChildOpen(2, true)); |
| assertNull(openHandler.getLastEventAndClear()); |
| assertNull(closeHandler.getLastEventAndClear()); |
| assertTrue(root.isChildOpen(2)); |
| assertFalse(child.isDestroyed()); |
| } |
| |
| /** |
| * Create an {@link AbstractCellTree} to test. |
| * |
| * @param <T> the data type of the root value |
| * @param model the {@link TreeViewModel} that backs the tree |
| * @param rootValue the root value |
| * @return a new {@link AbstractCellTree} |
| */ |
| protected abstract <T> AbstractCellTree createAbstractCellTree( |
| TreeViewModel model, T rootValue); |
| |
| @Override |
| protected void gwtSetUp() throws Exception { |
| model = new MockTreeViewModel(); |
| tree = createAbstractCellTree(model, ROOT_VALUE); |
| RootPanel.get().add(tree); |
| } |
| |
| @Override |
| protected void gwtTearDown() throws Exception { |
| RootPanel.get().remove(tree); |
| } |
| |
| /** |
| * Test the state of a {@link TreeNode}. |
| * |
| * @param node the node to test |
| * @param parent the expected parent |
| * @param index the expected index within the parent |
| * @param value the expected value |
| * @param isChildLeaf true if the node only contains leaf nodes |
| */ |
| private void testTreeNode(TreeNode node, TreeNode parent, int index, |
| Object value, boolean isChildLeaf) { |
| assertEquals(10, node.getChildCount()); |
| assertEquals(index, node.getIndex()); |
| assertEquals(parent, node.getParent()); |
| assertEquals(value, node.getValue()); |
| |
| // Test child values. |
| String prefix = (value == ROOT_VALUE) ? "" : value.toString(); |
| assertEquals(prefix + "a", node.getChildValue(0)); |
| assertEquals(prefix + "j", node.getChildValue(9)); |
| for (int i = 0; i < 10; i++) { |
| assertEquals(isChildLeaf, node.isChildLeaf(i)); |
| assertFalse(node.isChildOpen(i)); |
| } |
| |
| // Test children out of range. |
| try { |
| node.getChildValue(-1); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.getChildValue(10); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.isChildLeaf(-1); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.isChildLeaf(10); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.isChildOpen(-1); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.isChildOpen(10); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.setChildOpen(-1, true); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| try { |
| node.setChildOpen(10, true); |
| fail("Expected IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException e) { |
| // Expected. |
| } |
| } |
| } |