From 595e4677d7a3a9f6c92ed3f6a9b892b96e2d9d3d Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Sat, 23 Jan 2010 16:14:20 +0300 Subject: [PATCH] tree tests moved to community --- .../intellij/testFramework/IdeaTestUtil.java | 72 - platform/platform-impl/platform-impl.iml | 1 + .../AbstractTreeBuilderCrashTest.java | 277 ++++ .../treeView/AbstractTreeBuilderTest.java | 611 +++++++ .../ide/util/treeView/BaseTreeTestCase.java | 465 ++++++ .../treeView/FilteringTreeBuilderTest.java | 252 +++ .../ide/util/treeView/TreeUiTest.java | 1421 +++++++++++++++++ .../testFramework/FlyIdeaTestCase.java | 44 + .../testFramework/PlatformTestUtil.java | 76 +- 9 files changed, 3145 insertions(+), 74 deletions(-) create mode 100644 platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderCrashTest.java create mode 100644 platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderTest.java create mode 100644 platform/platform-impl/testSrc/com/intellij/ide/util/treeView/BaseTreeTestCase.java create mode 100644 platform/platform-impl/testSrc/com/intellij/ide/util/treeView/FilteringTreeBuilderTest.java create mode 100644 platform/platform-impl/testSrc/com/intellij/ide/util/treeView/TreeUiTest.java create mode 100644 platform/testFramework/src/com/intellij/testFramework/FlyIdeaTestCase.java diff --git a/java/testFramework/src/com/intellij/testFramework/IdeaTestUtil.java b/java/testFramework/src/com/intellij/testFramework/IdeaTestUtil.java index 4d94c995bdac..659e2ca38cf1 100644 --- a/java/testFramework/src/com/intellij/testFramework/IdeaTestUtil.java +++ b/java/testFramework/src/com/intellij/testFramework/IdeaTestUtil.java @@ -155,78 +155,6 @@ public class IdeaTestUtil extends PlatformTestUtil { assertDirectoriesEqual(dirAfter, dirBefore, CVS_FILE_FILTER); } - public static StringBuffer print(AbstractTreeStructure structure, - Object node, - int currentLevel, - Comparator comparator, - int maxRowCount, - char paddingChar) { - StringBuffer buffer = new StringBuffer(); - doPrint(buffer, currentLevel, node, structure, comparator, maxRowCount, 0, paddingChar); - return buffer; - } - - private static int doPrint(StringBuffer buffer, - int currentLevel, - Object node, - AbstractTreeStructure structure, - Comparator comparator, - int maxRowCount, - int currentLine, - char paddingChar) { - if (currentLine >= maxRowCount && maxRowCount != -1) return currentLine; - - StringUtil.repeatSymbol(buffer, paddingChar, currentLevel); - buffer.append(toString(node)).append("\n"); - currentLine++; - Object[] children = structure.getChildElements(node); - - if (comparator != null) { - ArrayList list = new ArrayList(Arrays.asList(children)); - Collections.sort(list, comparator); - children = list.toArray(new Object[list.size()]); - } - for (Object child : children) { - currentLine = doPrint(buffer, currentLevel + 1, child, structure, comparator, maxRowCount, currentLine, paddingChar); - } - - return currentLine; - } - - public static String print(Object[] objects) { - return print(Arrays.asList(objects)); - } - - public static String print(Collection c) { - StringBuilder result = new StringBuilder(); - for (Iterator iterator = c.iterator(); iterator.hasNext();) { - Object each = iterator.next(); - result.append(toString(each)); - if (iterator.hasNext()) { - result.append("\n"); - } - } - - return result.toString(); - } - - public static String print(ListModel model) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < model.getSize(); i++) { - result.append(toString(model.getElementAt(i))); - result.append("\n"); - } - return result.toString(); - } - - public static String print(JTree tree) { - return print(tree, false); - } - - public static void assertTreeStructureEquals(final AbstractTreeStructure treeStructure, final String expected) { - Assert.assertEquals(expected, print(treeStructure, treeStructure.getRootElement(), 0, null, -1, ' ').toString()); - } - public static void assertTiming(String message, long expected, long actual) { if (COVERAGE_ENABLED_BUILD) return; long expectedOnMyMachine = expected * Timings.MACHINE_TIMING / ETALON_TIMING; diff --git a/platform/platform-impl/platform-impl.iml b/platform/platform-impl/platform-impl.iml index ef26efdae698..3f163f7e753d 100644 --- a/platform/platform-impl/platform-impl.iml +++ b/platform/platform-impl/platform-impl.iml @@ -23,6 +23,7 @@ + diff --git a/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderCrashTest.java b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderCrashTest.java new file mode 100644 index 000000000000..bdedeebe06fe --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderCrashTest.java @@ -0,0 +1,277 @@ +package com.intellij.ide.util.treeView; + +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.treeStructure.Tree; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.util.ArrayList; + +public abstract class AbstractTreeBuilderCrashTest extends BaseTreeTestCase { + + private AbstractTreeStructure myStructure; + private DefaultTreeModel myTreeModel; + private CachedNode myRoot; + + protected AbstractTreeBuilderCrashTest(boolean yeild, boolean bg) { + super(yeild, bg); + } + + public void testElementMovedButNodeIsStillInStructure() throws Exception { + assertNodeMove(new Runnable() { + public void run() { + getBuilder().addSubtreeToUpdateByElement(myRoot.getChild("folder1")); + getBuilder().addSubtreeToUpdateByElement(myRoot.getChild("folder2")); + } + }); + } + + public void testElementMovedButNodeIsStillInStructure2() throws Exception { + assertNodeMove(new Runnable() { + public void run() { + getBuilder().addSubtreeToUpdateByElement(myRoot.getChild("folder2")); + getBuilder().addSubtreeToUpdateByElement(myRoot.getChild("folder1")); + } + }); + } + + private void assertNodeMove(final Runnable update) throws Exception { + myRoot = new CachedNode("root"); + final CachedNode folder1 = myRoot.addChild("folder1"); + final CachedNode file = folder1.addChild("file"); + final CachedNode folder2 = myRoot.addChild("folder2"); + + initTree(myRoot); + + getBuilder().updateFromRoot(); + getBuilder().select(file, null); + + assertTree( + "-root\n" + + " -folder1\n" + + " [file]\n" + + " folder2\n"); + + + folder1.myChildren.remove(file); + folder2.addChild("file"); + + update.run(); + + assertTree( + "-root\n" + + " folder1\n" + + " -folder2\n" + + " [file]\n"); + + } + + public void testNewUniqueChilden() throws Exception { + final boolean[] changes = new boolean[1]; + + final Node root = new Node("root", null) { + @Override + Node[] getChildren() { + if (changes[0]) { + return new Node[] {new Node("node1", this, "node1id", "changedNode1") { + @Override + Node[] getChildren() { + return new Node[] {new Node("newNode11", this, "node11id"), new Node("node13", this, "node13id")}; + } + }, new Node("node2", this, "node2id")}; + } else { + return new Node[] {new Node("node1", this, "node1id") { + @Override + Node[] getChildren() { + return new Node[] {new Node("node11", this, "node11id"), new Node("node12", this, "node12id")}; + } + }, new Node("node2", this, "node2id")}; + } + } + }; + + initTree(root); + + updateFromRoot(); + + assertTree("-root\n" + + " -node1\n" + + " node11\n" + + " node12\n" + + " node2\n"); + + collapsePath(new TreePath(myTreeModel.getRoot())); + getBuilder().expand(root, null); + assertTree("-root\n" + + " -node1\n" + + " node11\n" + + " node12\n" + + " node2\n"); + + + updateFromRoot(); + + assertTree("-root\n" + + " -node1\n" + + " node11\n" + + " node12\n" + + " node2\n"); + + changes[0] = true; + updateFromRoot(); + + assertTree("-root\n" + + " -node1\n" + + " newNode11\n" + + " node13\n" + + " node2\n"); + } + + private void initTree(final Node root) throws Exception { + myStructure = new BaseStructure() { + public Object getRootElement() { + return root; + } + + public Object[] doGetChildElements(final Object element) { + return ((Node)element).getChildren(); + } + + public Object getParentElement(final Object element) { + return ((Node)element).getParent(); + } + + @NotNull + public NodeDescriptor doCreateDescriptor(final Object element, final NodeDescriptor parentDescriptor) { + return (NodeDescriptor)element; + } + + public void commit() { + } + + public boolean hasSomethingToCommit() { + return false; + } + }; + + + myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); + myTree = new Tree(myTreeModel); + myTree.setRootVisible(false); + + initBuilder(new BaseTreeBuilder(myTree, myTreeModel, myStructure, AlphaComparator.INSTANCE) { + @Override + protected boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) { + return true; + } + + @Override + protected boolean isDisposeOnCollapsing(final NodeDescriptor nodeDescriptor) { + return false; + } + }); + + getBuilder().getUi().setCheckStructure(false); + Disposer.register(getRootDisposable(), getBuilder()); + + showTree(); + } + + class Node extends NodeDescriptor { + Node myParent; + String myId; + String myEqualityString; + String myComment; + + Node(String id, final Node parent) { + this(id, parent, id, null); + } + + Node(String id, final Node parent, String equalityString) { + this(id, parent, equalityString, null); + } + + Node(String id, final Node parent, String equalityString, String comment) { + super(null, parent); + myParent = parent; + myId = id; + myEqualityString = equalityString; + myComment = comment; + } + + Node getParent() { + return myParent; + } + + Node[] getChildren() { + return new Node[0]; + } + + public boolean update() { + return false; + } + + public Object getElement() { + return this; + } + + @Override + public String toString() { + return myId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Node)) return false; + + final Node node = (Node)o; + + if (!myEqualityString.equals(node.myEqualityString)) return false; + + return true; + } + + @Override + public int hashCode() { + return myId.hashCode(); + } + } + + class CachedNode extends Node { + private final ArrayList myChildren = new ArrayList(); + + CachedNode(final String id) { + super(id, null); + } + + CachedNode addChild(String id) { + final CachedNode node = new CachedNode(id); + myChildren.add(node); + node.myParent = this; + return node; + } + + CachedNode getChild(String id) { + for (CachedNode each : myChildren) { + if (id.equals(each.myId)) return each; + } + + return null; + } + + @Override + final Node[] getChildren() { + return myChildren.toArray(new Node[myChildren.size()]); + } + } + + public static class NoYieldNoBackground extends AbstractTreeBuilderCrashTest { + public NoYieldNoBackground() { + super(false, false); + } + } + +} diff --git a/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderTest.java b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderTest.java new file mode 100644 index 000000000000..3ebdf8d2238a --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/AbstractTreeBuilderTest.java @@ -0,0 +1,611 @@ +package com.intellij.ide.util.treeView; + +import com.intellij.openapi.util.Condition; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Time; +import com.intellij.util.WaitFor; +import com.intellij.util.containers.HashMap; +import junit.framework.Assert; +import org.jetbrains.annotations.NotNull; + +import javax.swing.event.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.util.*; + +abstract class AbstractTreeBuilderTest extends BaseTreeTestCase { + protected MyStructure myStructure; + + Node myRoot; + DefaultTreeModel myTreeModel; + Node myCom; + Node myOpenApi; + Node myIde; + Node myRunner; + Node myRcp; + + + boolean myEnsureSelection = false; + + AbstractTreeBuilderTest.Node myFabrique; + + + Map myElementUpdate = new TreeMap(); + ElementUpdateHook myElementUpdateHook; + + Map mySortedParent = new TreeMap(); + + NodeDescriptor.NodeComparator.Delegate myComparator; + Node myIntellij; + + protected AbstractTreeBuilderTest(boolean passthrougth) { + super(passthrougth); + } + + protected AbstractTreeBuilderTest(boolean yieldingUiBuild, boolean bgStructureBuilding) { + super(yieldingUiBuild, bgStructureBuilding); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + myComparator = new NodeDescriptor.NodeComparator.Delegate(new NodeDescriptor.NodeComparator() { + public int compare(NodeDescriptor o1, NodeDescriptor o2) { + return AlphaComparator.INSTANCE.compare(o1, o2); + } + }); + + mySortedParent.clear(); + + myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode(null)); + myTreeModel.addTreeModelListener(new TreeModelListener() { + public void treeNodesChanged(TreeModelEvent e) { + assertEdt(); + } + + public void treeNodesInserted(TreeModelEvent e) { + assertEdt(); + } + + public void treeNodesRemoved(TreeModelEvent e) { + assertEdt(); + } + + public void treeStructureChanged(TreeModelEvent e) { + assertEdt(); + } + }); + + + myTree = new Tree(myTreeModel); + myStructure = new MyStructure(); + myRoot = new Node(null, "/"); + + initBuilder(new MyBuilder()); + + myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + assertEdt(); + } + }); + + myTree.addTreeExpansionListener(new TreeExpansionListener() { + public void treeExpanded(TreeExpansionEvent event) { + assertEdt(); + } + + public void treeCollapsed(TreeExpansionEvent event) { + assertEdt(); + } + }); + } + + @Override + protected void tearDown() throws Exception { + myElementUpdate.clear(); + myElementUpdateHook = null; + super.tearDown(); + } + + + Node removeFromParentButKeepRef(NodeElement child) { + NodeElement parent = (NodeElement)myStructure.getParentElement(child); + AbstractTreeBuilderTest.Node node = myStructure.getNodeFor(parent).remove(child, false); + Assert.assertEquals(parent, myStructure.getParentElement(child)); + Assert.assertFalse(Arrays.asList(myStructure._getChildElements(parent, false)).contains(child)); + + return node; + } + + void assertSorted(String expected) { + Iterator keys = mySortedParent.keySet().iterator(); + StringBuffer result = new StringBuffer(); + while (keys.hasNext()) { + String each = keys.next(); + result.append(each); + int count = mySortedParent.get(each); + if (count > 1) { + result.append(" (" + count + ")"); + } + + if (keys.hasNext()) { + result.append("\n"); + } + } + + Assert.assertEquals(expected + "\n", result + "\n"); + mySortedParent.clear(); + } + + private void addEntry(String value) { + if (mySortedParent.containsKey(value)) { + mySortedParent.put(value, mySortedParent.get(value) + 1); + } else { + mySortedParent.put(value, 0); + } + } + + void assertUpdates(String expected) { + List entries = Arrays.asList(myElementUpdate.values().toArray()); + Assert.assertEquals(expected + "\n", PlatformTestUtil.print(entries) + "\n"); + myElementUpdate.clear(); + } + + public void _testNoUpdateIfHidden() throws Exception { + final ArrayList processedNodes = new ArrayList(); + final boolean[] toHide = new boolean[] {false}; + + myElementUpdateHook = new ElementUpdateHook() { + public void onElementAction(String action, Object element) { + if (toHide[0]) { + if ("jetbrains".equals(element.toString())) { + getBuilder().getUi().deactivate(); + } + } + } + }; + buildStructure(myRoot); + + waitBuilderToCome(); + processedNodes.clear(); + + + buildNode("/", false); + assertTree( + "-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + Assert.assertEquals("/\n" + + "com\n" + + "jetbrains\n" + + "xunit\n" + + "org\n" + + "intellij\n" + + "fabrique\n" + + "runner\n" + + "eclipse", PlatformTestUtil.print(processedNodes)); + + + processedNodes.clear(); + toHide[0] = true; + updateFromRoot(); + + Assert.assertEquals("/\n" + + "com\n" + + "intellij\n" + + "jetbrains", PlatformTestUtil.print(processedNodes)); + + processedNodes.clear(); + toHide[0] = false; + + + showTree(); + + assertTree( + "-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + Assert.assertEquals("/\n" + + "com\n" + + "intellij\n" + + "jetbrains\n" + + "fabrique\n" + + "org\n" + + "eclipse\n" + + "xunit\n" + + "runner", PlatformTestUtil.print(processedNodes)); + + } + + + void buildStructure(final Node root) throws Exception { + doAndWaitForBuilder(new Runnable() { + public void run() { + myCom = root.addChild("com"); + myIntellij = myCom.addChild("intellij"); + myOpenApi = myIntellij.addChild("openapi"); + myFabrique = root.addChild("jetbrains").addChild("fabrique"); + myIde = myFabrique.addChild("ide"); + myRunner = root.addChild("xunit").addChild("runner"); + myRcp = root.addChild("org").addChild("eclipse").addChild("rcp"); + + getBuilder().getUi().activate(true); + } + }); + } + + protected void activate() throws Exception { + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().getUi().activate(true); + } + }); + } + + + void hideTree() throws Exception { + Assert.assertFalse(getMyBuilder().myWasCleanedUp); + + invokeLaterIfNeeded(new Runnable() { + public void run() { + getBuilder().getUi().deactivate(); + } + }); + + final WaitFor waitFor = new WaitFor() { + protected boolean condition() { + return getMyBuilder().myWasCleanedUp || myCancelRequest != null; + } + }; + + if (myCancelRequest != null) { + throw new Exception(myCancelRequest); + } + + waitFor.assertCompleted("Tree cleanup was not performed"); + + Assert.assertTrue(getMyBuilder().myWasCleanedUp); + } + + void buildNode(String elementText, boolean select) throws Exception { + buildNode(new NodeElement(elementText), select); + } + + void buildNode(Node node, boolean select) throws Exception { + buildNode(node.myElement, select); + } + + void buildNode(final NodeElement element, final boolean select) throws Exception { + buildNode(element, select, true); + } + + + void buildNode(final NodeElement element, final boolean select, final boolean addToSelection) throws Exception { + final boolean[] done = new boolean[] {false}; + + doAndWaitForBuilder(new Runnable() { + public void run() { + if (select) { + getBuilder().select(element, new Runnable() { + public void run() { + done[0] = true; + } + }, addToSelection); + } else { + getBuilder().expand(element, new Runnable() { + public void run() { + done[0] = true; + } + }); + } + } + }, new Condition() { + public boolean value(Object o) { + return done[0]; + } + }); + + Assert.assertNotNull(findNode(element, select)); + } + + + DefaultMutableTreeNode findNode(String elementText, boolean shouldBeSelected) { + return findNode(new NodeElement(elementText), shouldBeSelected); + } + + DefaultMutableTreeNode findNode(NodeElement element, boolean shouldBeSelected) { + return findNode((DefaultMutableTreeNode)myTree.getModel().getRoot(), element, shouldBeSelected); + } + + private DefaultMutableTreeNode findNode(DefaultMutableTreeNode treeNode, NodeElement toFind, boolean shouldBeSelected) { + final Object object = treeNode.getUserObject(); + Assert.assertNotNull(object); + if (!(object instanceof NodeDescriptor)) return null; + final NodeElement element = (NodeElement)((NodeDescriptor)object).getElement(); + if (toFind.equals(element)) return treeNode; + + for (int i = 0; i < treeNode.getChildCount(); i++) { + final DefaultMutableTreeNode result = findNode((DefaultMutableTreeNode)treeNode.getChildAt(i), toFind, shouldBeSelected); + if (result != null) { + if (shouldBeSelected) { + final TreePath path = new TreePath(result.getPath()); + Assert.assertTrue("Path should be selected: " + path, myTree.isPathSelected(path)); + } + return result; + } + } + + return null; + } + + + class Node { + + final NodeElement myElement; + final ArrayList myChildElements = new ArrayList(); + + Node(Node parent, String textName) { + this(parent, new NodeElement(textName)); + } + + Node(Node parent, NodeElement name) { + myElement = name; + setParent(parent); + } + + private void setParent(Node parent) { + myStructure.register(parent != null ? parent.myElement : null, this); + } + + public NodeElement getElement() { + return myElement; + } + + public Node addChild(Node node) { + myChildElements.add(node); + node.setParent(this); + return node; + } + + public Node addChild(String name) { + final Node node = new Node(this, name); + myChildElements.add(node); + return node; + } + + @Override + public String toString() { + return myElement.toString(); + } + + public void removeAll() { + myChildElements.clear(); + } + + public Node getChildNode(String name) { + for (Node each : myChildElements) { + if (name.equals(each.myElement.myName)) return each; + } + + return null; + } + + public Object[] getChildElements() { + Object[] elements = new Object[myChildElements.size()]; + for (int i = 0; i < myChildElements.size(); i++) { + elements[i] = myChildElements.get(i).myElement; + } + return elements; + } + + public void delete() { + final NodeElement parent = (NodeElement)myStructure.getParentElement(myElement); + Assert.assertNotNull(myElement.toString(), parent); + + myStructure.getNodeFor(parent).remove(myElement, true); + } + + private Node remove(final NodeElement name, boolean removeRefToParent) { + final Iterator kids = myChildElements.iterator(); + Node removed = null; + while (kids.hasNext()) { + Node each = kids.next(); + if (name.equals(each.myElement)) { + kids.remove(); + removed = each; + break; + } + } + + if (removeRefToParent) { + myStructure.myChild2Parent.remove(name); + } + + return removed; + } + } + + class MyStructure extends BaseStructure { + + private final Map myChild2Parent = new HashMap(); + private final Map myElement2Node = new HashMap(); + private Set myLeaves = new HashSet(); + + public Object getRootElement() { + return myRoot.myElement; + } + + public void reinitRoot(Node root) { + myRoot = root; + myElement2Node.clear(); + myLeaves.clear(); + myElement2Node.put(root.myElement, root); + } + + @Override + public Object[] doGetChildElements(Object element) { + + onElementAction("getChildren", (NodeElement)element); + + final AbstractTreeBuilderTest.Node node = myElement2Node.get((NodeElement)element); + return node.getChildElements(); + } + + private Node getNodeFor(Object element) { + return myElement2Node.get(element); + } + + public Object getParentElement(final Object element) { + return myChild2Parent.get((NodeElement)element); + } + + + @Override + public boolean isAlwaysLeaf(Object element) { + return myLeaves.contains(element); + } + + public void addLeaf(NodeElement element) { + myLeaves.add(element); + } + + public void removeLeaf(NodeElement element) { + myLeaves.remove(element); + } + + @NotNull + public NodeDescriptor doCreateDescriptor(final Object element, final NodeDescriptor parentDescriptor) { + return new NodeDescriptor(null, parentDescriptor) { + public boolean update() { + onElementAction("update", (NodeElement)element); + return true; + } + + public Object getElement() { + return element; + } + + @Override + public String toString() { + return getElement().toString(); + } + }; + } + + public void register(final NodeElement parent, final Node child) { + myChild2Parent.put(child.myElement, parent); + myElement2Node.put(child.myElement, child); + } + + public void clear() { + myChild2Parent.clear(); + myElement2Node.clear(); + myElement2Node.put(myRoot.myElement, myRoot); + } + + public Node getNodeFor(NodeElement element) { + return myElement2Node.get(element); + } + } + + class MyBuilder extends BaseTreeBuilder { + + public MyBuilder() { + super(AbstractTreeBuilderTest.this.myTree, AbstractTreeBuilderTest.this.myTreeModel, AbstractTreeBuilderTest.this.myStructure, myComparator, + false); + + initRootNode(); + + getUi().setJantorPollPeriod(Time.SECOND * 2); + } + + + @Override + protected void sortChildren(Comparator nodeComparator, DefaultMutableTreeNode node, ArrayList children) { + super.sortChildren(nodeComparator, node, children); + addEntry(node.toString()); + } + + @Override + public boolean isToEnsureSelectionOnFocusGained() { + return myEnsureSelection; + } + } + + private void onElementAction(String action, NodeElement element) { + ElementEntry entry = myElementUpdate.get(element); + if (entry == null) { + entry = new ElementEntry(element); + myElementUpdate.put(element, entry); + } + entry.onElementAction(action); + + if (myElementUpdateHook != null) { + myElementUpdateHook.onElementAction(action, element); + } + } + + static interface ElementUpdateHook { + void onElementAction(String action, Object element); + } + + private class ElementEntry { + + NodeElement myElement; + + int myUpdateCount; + int myGetChildrenCount; + + private ElementEntry(NodeElement element) { + myElement = element; + } + + void onElementAction(String action) { + try { + if ("update".equals(action)) { + myUpdateCount++; + } else if ("getChildren".equals(action)) { + Assert.assertTrue("getChildren() is called before update(), node=" + myElement, myUpdateCount > 0); + myGetChildrenCount++; + } + } + catch (Throwable e) { + myCancelRequest = e; + } + } + + @Override + public String toString() { + return (myElement + ": " + asString(myUpdateCount, "update") + " " + asString(myGetChildrenCount, "getChildren")).trim(); + } + + private String asString(int count, String text) { + if (count == 0) return ""; + return count > 1 ? text + " (" + count + ")" : text; + } + } + + + + MyBuilder getMyBuilder() { + return (MyBuilder)getBuilder(); + } + + interface TreeAction { + void run(Runnable onDone); + } + + TreePath getPath(String s) { + return new TreePath(findNode(s, false).getPath()); + } + +} + \ No newline at end of file diff --git a/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/BaseTreeTestCase.java b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/BaseTreeTestCase.java new file mode 100644 index 000000000000..827be17f371e --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/BaseTreeTestCase.java @@ -0,0 +1,465 @@ +package com.intellij.ide.util.treeView; + +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SimpleTimer; +import com.intellij.testFramework.FlyIdeaTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.WaitFor; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.ComparableObject; +import com.intellij.util.ui.update.MergingUpdateQueue; +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +abstract class BaseTreeTestCase extends FlyIdeaTestCase { + + private BaseTreeBuilder myBuilder; + Tree myTree; + + Throwable myCancelRequest; + boolean myReadyRequest; + + private boolean myYieldingUiBuild; + private boolean myBgStructureBuilding; + private boolean myPassthroughMode; + + final Set myAutoExpand = new HashSet(); + final Set myAlwaysShowPlus = new HashSet(); + boolean mySmartExpand; + private Thread myTestThread; + + protected BaseTreeTestCase(boolean passthrougth) { + this(false, false); + myPassthroughMode = passthrougth; + } + + protected BaseTreeTestCase(boolean yieldingUiBuild, boolean bgStructureBuilding) { + myYieldingUiBuild = yieldingUiBuild; + myBgStructureBuilding = bgStructureBuilding; + } + + void doAndWaitForBuilder(final Runnable runnable, final Condition condition) throws Exception { + final Ref started = new Ref(); + invokeLaterIfNeeded(new Runnable() { + public void run() { + started.set(true); + runnable.run(); + } + }); + + waitBuilderToCome(new Condition() { + public boolean value(Object o) { + return started.get() && condition.value(null); + } + }); + } + + void waitBuilderToCome() { + try { + waitBuilderToCome(Condition.TRUE); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + void waitBuilderToCome(final Condition condition) throws Exception { + boolean success = new WaitFor(600000) { + protected boolean condition() { + final boolean[] ready = new boolean[]{false}; + invokeAndWaitIfNeeded(new Runnable() { + public void run() { + AbstractTreeUi ui = getBuilder().getUi(); + if (ui == null) { + ready[0] = true; + return; + } + + ready[0] = (myCancelRequest != null || myReadyRequest) || (condition.value(null) && (ui.isReady())); + } + }); + + return ready[0]; + } + }.isConditionRealized(); + + if (myCancelRequest != null) { + Thread.dumpStack(); + throw new Exception(myCancelRequest); + } + + Assert.assertTrue(getBuilder().getUi().getNodeActions().isEmpty()); + + Assert.assertTrue(success); + } + + void expand(final TreePath p) throws Exception { + invokeAndWaitIfNeeded(new Runnable() { + public void run() { + myTree.expandPath(p); + } + }); + waitBuilderToCome(); + } + + void collapsePath(final TreePath p) throws Exception { + invokeAndWaitIfNeeded(new Runnable() { + public void run() { + myTree.collapsePath(p); + } + }); + waitBuilderToCome(); + } + + AbstractTreeBuilder getBuilder() { + return myBuilder; + } + + protected void initBuilder(BaseTreeBuilder builder) { + myBuilder = builder; + myBuilder.setCanYieldUpdate(myYieldingUiBuild); + myBuilder.setPassthroughMode(myPassthroughMode); + } + + + class BaseTreeBuilder extends AbstractTreeBuilder { + volatile boolean myWasCleanedUp; + + public BaseTreeBuilder(JTree tree, + DefaultTreeModel treeModel, + AbstractTreeStructure treeStructure, + @Nullable Comparator comparator) { + super(tree, treeModel, treeStructure, comparator); + } + + public BaseTreeBuilder(JTree tree, + DefaultTreeModel treeModel, + AbstractTreeStructure treeStructure, + @Nullable Comparator comparator, + boolean updateIfInactive) { + super(tree, treeModel, treeStructure, comparator, updateIfInactive); + } + + public BaseTreeBuilder() { + } + + + @Override + protected final boolean updateNodeDescriptor(NodeDescriptor descriptor) { + checkThread(); + + int delay = getNodeDescriptorUpdateDelay(); + if (delay > 0) { + try { + Thread.currentThread().sleep(delay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + + return super.updateNodeDescriptor(descriptor); + } + + protected boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) { + return myAutoExpand.contains(nodeDescriptor.getElement()); + } + + @Override + protected boolean isSmartExpand() { + return mySmartExpand; + } + + + @Override + protected boolean isAlwaysShowPlus(NodeDescriptor descriptor) { + return myAlwaysShowPlus.contains(descriptor.getElement()); + } + + @Override + protected AbstractTreeUpdater createUpdater() { + return _createUpdater(this); + } + + @Override + protected void updateAfterLoadedInBackground(Runnable runnable) { + _updateAfterLoadedInBackground(runnable); + } + + @Override + protected void runBackgroundLoading(Runnable runnable) { + _runBackgroundLoading(runnable); + } + + + + @Override + public void cleanUp() { + super.cleanUp(); + myWasCleanedUp = true; + } + + + @Override + protected AbstractTreeUi createUi() { + return _createUi(); + } + } + + void _updateAfterLoadedInBackground(Runnable runnable) { + SwingUtilities.invokeLater(runnable); + } + + void _runBackgroundLoading(Runnable runnable) { + SimpleTimer.getInstance().setUp(runnable, getChildrenLoadingDelay()); + } + + + AbstractTreeUi _createUi() { + return new AbstractTreeUi() { + @Override + protected void yield(Runnable runnable) { + SimpleTimer.getInstance().setUp(runnable, 100); + } + + @Override + protected boolean canYield() { + return myYieldingUiBuild; + } + + @Override + protected void runOnYieldingDone(Runnable onDone) { + SwingUtilities.invokeLater(onDone); + } + }; + } + + AbstractTreeUpdater _createUpdater(AbstractTreeBuilder builder) { + final AbstractTreeUpdater updater = new AbstractTreeUpdater(builder) { + @Override + protected void invokeLater(Runnable runnable) { + runnable.run(); + } + + @Override + protected boolean isEdt() { + return SwingUtilities.isEventDispatchThread(); + } + }; + updater.setModalityStateComponent(MergingUpdateQueue.ANY_COMPONENT); + return updater; + } + + abstract class BaseStructure extends AbstractTreeStructure { + + @Override + public boolean isToBuildChildrenInBackground(Object element) { + return myBgStructureBuilding; + } + + @Override + public void commit() { + } + + @Override + public boolean hasSomethingToCommit() { + return false; + } + + @NotNull + @Override + public final NodeDescriptor createDescriptor(Object element, NodeDescriptor parentDescriptor) { + checkThread(); + return doCreateDescriptor(element, parentDescriptor); + } + + @NotNull + public abstract NodeDescriptor doCreateDescriptor(Object element, NodeDescriptor parentDescriptor); + + @Override + public final Object[] getChildElements(Object element) { + return _getChildElements(element, true); + } + + public final Object[] _getChildElements(Object element, boolean checkThread) { + if (checkThread) { + checkThread(); + } + + return doGetChildElements(element); + } + + public abstract Object[] doGetChildElements(Object element); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + myCancelRequest = null; + myReadyRequest = false; + mySmartExpand = false; + myAutoExpand.clear(); + myAlwaysShowPlus.clear(); + myTestThread = Thread.currentThread(); + } + + @Override + protected void tearDown() throws Exception { + invokeLaterIfNeeded(new Runnable() { + public void run() { + if (getBuilder() != null) { + Disposer.dispose(getBuilder()); + } + } + }); + + super.tearDown(); + } + + void assertTree(final String expected) throws Exception { + waitBuilderToCome(); + Assert.assertEquals(expected, PlatformTestUtil.print(myTree, true)); + } + + void doAndWaitForBuilder(final Runnable runnable) throws Exception { + doAndWaitForBuilder(runnable, Condition.TRUE); + } + + + void updateFromRoot() throws Exception { + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().updateFromRoot(); + } + }); + } + + void updateFrom(final NodeElement element) throws Exception { + updateFrom(element, false); + } + + void updateFrom(final NodeElement element, final boolean forceResort) throws Exception { + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().queueUpdateFrom(element, forceResort); + } + }); + } + + void showTree() throws Exception { + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().getUi().activate(true); + } + }); + } + + void select(final Object element, final boolean addToSelection) throws Exception { + select(new Object[] {element}, addToSelection); + } + + void select(final Object[] elements, final boolean addToSelection) throws Exception { + final Ref done = new Ref(false); + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().select(elements, new Runnable() { + public void run() { + done.set(true); + } + }, addToSelection); + } + }, new Condition() { + public boolean value(Object o) { + return done.get(); + } + }); + } + + protected final boolean isYieldingUiBuild() { + return myYieldingUiBuild; + } + + protected final boolean isBgStructureBuilding() { + return myBgStructureBuilding; + } + + protected int getChildrenLoadingDelay() { + return 0; + } + + protected int getNodeDescriptorUpdateDelay() { + return 0; + } + + private void checkThread() { + String message = "Wrong thread used for query structure, thread=" + Thread.currentThread(); + + if (myPassthroughMode) { + Assert.assertSame(message, myTestThread, Thread.currentThread()); + } else { + if (isBgStructureBuilding()) { + Assert.assertFalse(message, EventQueue.isDispatchThread()); + } else { + Assert.assertTrue(message, EventQueue.isDispatchThread()); + } + } + } + + protected final void invokeLaterIfNeeded(Runnable runnable) { + if (myPassthroughMode) { + runnable.run(); + } else { + UIUtil.invokeLaterIfNeeded(runnable); + } + } + + protected final void invokeAndWaitIfNeeded(Runnable runnable) { + if (myPassthroughMode) { + runnable.run(); + } else { + UIUtil.invokeAndWaitIfNeeded(runnable); + } + } + + protected final void assertEdt() { + if (myPassthroughMode) { + checkThread(); + } else if (!EventQueue.isDispatchThread()) { + myCancelRequest = new AssertionFailedError("Must be event dispatch thread"); + throw new RuntimeException(myCancelRequest); + } + } + + public static class NodeElement extends ComparableObject.Impl implements Comparable{ + + final String myName; + + public NodeElement(String name) { + super(name); + myName = name; + } + + public String toString() { + return myName; + } + + public int compareTo(NodeElement o) { + return myName.compareTo(o.myName); + } + } + +} diff --git a/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/FilteringTreeBuilderTest.java b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/FilteringTreeBuilderTest.java new file mode 100644 index 000000000000..2f085e64d422 --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/FilteringTreeBuilderTest.java @@ -0,0 +1,252 @@ +package com.intellij.ide.util.treeView; + +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import com.intellij.ui.TreeUIHelper; +import com.intellij.ui.speedSearch.ElementFilter; +import com.intellij.ui.treeStructure.*; +import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; +import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; + +import java.util.LinkedHashMap; + +public class FilteringTreeBuilderTest extends BaseTreeTestCase { + private FilteringTreeBuilder myBuilder; + private MyFilter myFilter; + private Node myRoot; + private SimpleTreeStructure myStructure; + + public FilteringTreeBuilderTest() { + super(false, false); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myTree = new SimpleTree() { + @Override + protected void configureUiHelper(final TreeUIHelper helper) { + } + }; + + myFilter = new MyFilter(); + myRoot = new Node(null, "/"); + myStructure = new SimpleTreeStructure.Impl(myRoot); + } + + private void initBuilder() throws Exception { + myBuilder = new FilteringTreeBuilder(null, myTree, myFilter, myStructure, AlphaComparator.INSTANCE) { + @Override + protected AbstractTreeUpdater createUpdater() { + return _createUpdater(this); + } + }; + + showTree(); + + Disposer.register(getRootDisposable(), myBuilder); + } + + public void testFilter() throws Exception { + myTree.setRootVisible(false); + + final Node f1 = myRoot.addChild("folder1"); + f1.addChild("file11"); + f1.addChild("file12"); + Node f11 = f1.addChild("folder11"); + f11.addChild("element111"); + myRoot.addChild("folder2").addChild("file21"); + + initBuilder(); + + assertTree("-/\n" + + " -folder1\n" + + " file11\n" + + " file12\n" + + " -folder11\n" + + " element111\n" + + " -folder2\n" + + " file21\n"); + + update("", findNode("file11")); + assertTree("-/\n" + + " -folder1\n" + + " [file11]\n" + + " file12\n" + + " -folder11\n" + + " element111\n" + + " -folder2\n" + + " file21\n"); + + update("f", null); + assertTree("-/\n" + + " -folder1\n" + + " [file11]\n" + + " file12\n" + + " folder11\n" + + " -folder2\n" + + " file21\n"); + + update("fo", null); + assertTree("-/\n" + + " -folder1\n" + + " [folder11]\n" + + " folder2\n"); + + update("fo_", null); + assertTree("+/\n"); + + update("", null); + assertTree("-/\n" + + " -[folder1]\n" + + " file11\n" + + " file12\n" + + " -folder11\n" + + " element111\n" + + " -folder2\n" + + " file21\n"); + + + select("element111"); + assertTree("-/\n" + + " -folder1\n" + + " file11\n" + + " file12\n" + + " -folder11\n" + + " [element111]\n" + + " -folder2\n" + + " file21\n"); + + update("folder2", null); + assertTree("-/\n" + + " [folder2]\n"); + + update("", null); + assertTree("-/\n" + + " -folder1\n" + + " file11\n" + + " file12\n" + + " -folder11\n" + + " element111\n" + + " -[folder2]\n" + + " file21\n"); + + update("file1", null); + assertTree("-/\n" + + " -[folder1]\n" + + " file11\n" + + " file12\n"); + + select("file12"); + assertTree("-/\n" + + " -folder1\n" + + " file11\n" + + " [file12]\n"); + + update("", null); + assertTree("-/\n" + + " -folder1\n" + + " file11\n" + + " [file12]\n" + + " -folder11\n" + + " element111\n" + + " -folder2\n" + + " file21\n"); + + } + + private void select(String element) throws Exception { + FilteringTreeStructure.Node node = myBuilder.getVisibleNodeFor(findNode(element)); + select(new Object[] {node}, false); + } + + private void update(final String text, final Object selection) throws Exception { + final ActionCallback result = new ActionCallback(); + doAndWaitForBuilder(new Runnable() { + public void run() { + myFilter.update(text, selection).notify(result); + } + }, new Condition() { + public boolean value(Object o) { + return result.isProcessed(); + } + }); + } + + private class Node extends CachingSimpleNode { + + private final LinkedHashMap myKids = new LinkedHashMap(); + + private Node(final SimpleNode aParent, String name) { + super(aParent); + myName = name; + } + + public Node addChild(String name) { + if (!myKids.containsKey(name)) { + myKids.put(name, new Node(this, name)); + } + + return myKids.get(name); + } + + @Override + protected void doUpdate() { + setPlainText(myName); + } + + protected SimpleNode[] buildChildren() { + return myKids.values().toArray(new Node[myKids.size()]); + } + + @Override + public String toString() { + return myName; + } + + @Override + protected void updateFileStatus() { + + } + } + + private Object findNode(final String name) { + final Ref node = new Ref(); + ((SimpleTree)myTree).accept(myBuilder, new SimpleNodeVisitor() { + public boolean accept(final SimpleNode simpleNode) { + if (name.equals(simpleNode.toString())) { + node.set(myBuilder.getOriginalNode(simpleNode)); + return true; + } else { + return false; + } + } + }); + + return node.get(); + } + + + private class MyFilter extends ElementFilter.Active.Impl { + + String myPattern = ""; + + public boolean shouldBeShowing(final Object value) { + return value.toString().startsWith(myPattern); + } + + public ActionCallback update(final String pattern, Object selection) { + myPattern = pattern; + return fireUpdate(selection, true, false); + } + } + + @Override + AbstractTreeBuilder getBuilder() { + return myBuilder; + } +} + + diff --git a/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/TreeUiTest.java b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/TreeUiTest.java new file mode 100644 index 000000000000..3e0a0d03fd0a --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/ide/util/treeView/TreeUiTest.java @@ -0,0 +1,1421 @@ +package com.intellij.ide.util.treeView; + +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import com.intellij.util.Time; +import com.intellij.util.WaitFor; +import com.intellij.util.ui.UIUtil; +import junit.framework.TestSuite; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; + +public class TreeUiTest extends AbstractTreeBuilderTest { + + public TreeUiTest(boolean passthrougth) { + super(passthrougth); + } + + public TreeUiTest(boolean yieldingUiBuild, boolean bgStructureBuilding) { + super(yieldingUiBuild, bgStructureBuilding); + } + + public void testEmptyInvisibleRoot() throws Exception { + myTree.setRootVisible(false); + showTree(); + assertTree("+/\n"); + + updateFromRoot(); + assertTree("+/\n"); + + + buildNode("/", false); + assertTree("+/\n"); + + myTree.setRootVisible(true); + buildNode("/", false); + assertTree("/\n"); + } + + public void testVisibleRoot() throws Exception { + myTree.setRootVisible(true); + buildStructure(myRoot); + assertTree("+/\n"); + + updateFromRoot(); + assertTree("+/\n"); + } + + public void testInvisibleRoot() throws Exception { + myTree.setRootVisible(false); + buildStructure(myRoot); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + collapsePath(new TreePath(myTreeModel.getRoot())); + assertTree("+/\n"); + + updateFromRoot(); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + buildNode("com", true); + assertTree("-/\n" + + " +[com]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myRoot.removeAll(); + updateFromRoot(); + + assertTree("+/\n"); + + } + + public void testAutoExpand() throws Exception { + buildStructure(myRoot); + assertTree("+/\n"); + + myAutoExpand.add(new NodeElement("/")); + buildStructure(myRoot); + + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + myAutoExpand.add(new NodeElement("jetbrains")); + updateFromRoot(); + + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " +fabrique\n" + + " +org\n" + + " +xunit\n"); + + collapsePath(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + updateFrom(new NodeElement("org")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + updateFrom(new NodeElement("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " +fabrique\n" + + " +org\n" + + " +xunit\n"); + } + + public void testAutoExpandDeep() throws Exception { + myTree.setRootVisible(false); + //myAutoExpand.add(new NodeElement("jetbrains")); + myAutoExpand.add(new NodeElement("fabrique")); + + + buildStructure(myRoot); + //assertTree("+/\n"); + + expand(getPath("/")); + expand(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + collapsePath(getPath("/")); + assertTree("+/\n"); + + expand(getPath("/")); + expand(getPath("jetbrains")); + + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + collapsePath(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + } + + + public void testAutoExpandInNonVisibleNode() throws Exception { + myAutoExpand.add(new NodeElement("fabrique")); + buildStructure(myRoot); + + expand(getPath("/")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + public void testSmartExpand() throws Exception { + mySmartExpand = true; + buildStructure(myRoot); + assertTree("+/\n"); + + expand(getPath("/")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + collapsePath(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + updateFromRoot(); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + mySmartExpand = false; + collapsePath(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("jetbrains")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " +fabrique\n" + + " +org\n" + + " +xunit\n"); + } + + + public void testClear() throws Exception { + getBuilder().getUi().setClearOnHideDelay(10 * Time.SECOND); + + buildStructure(myRoot); + + assertTree("+/\n"); + + final DefaultMutableTreeNode openApiNode = findNode("openapi", false); + final DefaultMutableTreeNode ideNode = findNode("ide", false); + final DefaultMutableTreeNode runnerNode = findNode("runner", false); + final DefaultMutableTreeNode rcpNode = findNode("rcp", false); + + assertNull(openApiNode); + assertNull(ideNode); + assertNull(runnerNode); + assertNull(rcpNode); + + buildNode(myOpenApi, true); + buildNode(myIde, true); + buildNode(myRunner, false); + + hideTree(); + + assertNull(findNode("openapi", true)); + assertNull(findNode("ide", true)); + assertNull(findNode("runner", false)); + assertNull(findNode("rcp", false)); + + + showTree(); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " -fabrique\n" + + " [ide]\n" + + " +org\n" + + " -xunit\n" + + " runner\n"); + + getMyBuilder().myWasCleanedUp = false; + hideTree(); + showTree(); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " -fabrique\n" + + " [ide]\n" + + " +org\n" + + " -xunit\n" + + " runner\n"); + + + buildNode(myFabrique.myElement, true, false); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " openapi\n" + + " -jetbrains\n" + + " -[fabrique]\n" + + " ide\n" + + " +org\n" + + " -xunit\n" + + " runner\n"); + } + + public void testUpdateRestoresState() throws Exception { + buildStructure(myRoot); + + buildNode(myOpenApi, true); + buildNode(myIde, true); + buildNode(myRunner, false); + + waitBuilderToCome(); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " -fabrique\n" + + " [ide]\n" + + " +org\n" + + " -xunit\n" + + " runner\n"); + + myRoot.removeAll(); + myStructure.clear(); + + final AbstractTreeBuilderTest.Node newRoot = myRoot.addChild("newRoot"); + + buildStructure(newRoot); + + updateFromRoot(); + assertTree("-/\n" + + " -newRoot\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " -fabrique\n" + + " [ide]\n" + + " +org\n" + + " -xunit\n" + + " runner\n"); + } + + + public void testSelect() throws Exception { + buildStructure(myRoot); + assertTree( + "+/\n"); + + + buildNode(myOpenApi, true); + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + buildNode("fabrique", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " +[fabrique]\n" + + " +org\n" + + " +xunit\n"); + } + + public void testCallbackOnceOnSelect() throws Exception { + buildStructure(myRoot); + + assertCallbackOnce(new TreeAction() { + public void run(Runnable onDone) { + getMyBuilder().select(new Object[] {new NodeElement("intellij"), new NodeElement("fabrique")}, onDone); + } + }); + + assertTree( + "-/\n" + + " -com\n" + + " +[intellij]\n" + + " -jetbrains\n" + + " +[fabrique]\n" + + " +org\n" + + " +xunit\n"); + + } + + public void testCallbackOnceOnExpand() throws Exception { + buildStructure(myRoot); + + assertCallbackOnce(new TreeAction() { + public void run(Runnable onDone) { + getMyBuilder().expand(new Object[] {new NodeElement("intellij"), new NodeElement("fabrique")}, onDone); + } + }); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " openapi\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + } + + + public void testNoInfiniteAutoExpand() throws Exception { + mySmartExpand = false; + + assertNoInfiniteAutoExpand(new Runnable() { + public void run() { + myAutoExpand.add(new NodeElement("level2")); + myAutoExpand.add(new NodeElement("level3")); + myAutoExpand.add(new NodeElement("level4")); + myAutoExpand.add(new NodeElement("level5")); + myAutoExpand.add(new NodeElement("level6")); + myAutoExpand.add(new NodeElement("level7")); + myAutoExpand.add(new NodeElement("level8")); + myAutoExpand.add(new NodeElement("level9")); + myAutoExpand.add(new NodeElement("level10")); + myAutoExpand.add(new NodeElement("level11")); + myAutoExpand.add(new NodeElement("level12")); + myAutoExpand.add(new NodeElement("level13")); + myAutoExpand.add(new NodeElement("level14")); + myAutoExpand.add(new NodeElement("level15")); + } + }); + } + + public void testNoInfiniteSmartExpand() throws Exception { + mySmartExpand = false; + + assertNoInfiniteAutoExpand(new Runnable() { + public void run() { + mySmartExpand = true; + } + }); + } + + private void assertNoInfiniteAutoExpand(final Runnable enableExpand) throws Exception { + class Level extends Node { + + int myLevel; + + Level(Node parent, int level) { + super(parent, "level" + level); + myLevel = level; + } + + @Override + public Object[] getChildElements() { + if (super.getChildElements().length == 0) { + addChild(new Level(this, myLevel + 1)); + } + + return super.getChildElements(); + } + } + + myRoot.addChild(new Level(myRoot, 0)); + + activate(); + buildNode("level0", false); + + assertTree("-/\n" + + " -level0\n" + + " +level1\n"); + + enableExpand.run(); + + expand(getPath("level1")); + + assertTree("-/\n" + + " -level0\n" + + " -level1\n" + + " -level2\n" + + " -level3\n" + + " -level4\n" + + " -level5\n" + + " +level6\n"); + + expand(getPath("level6")); + assertTree("-/\n" + + " -level0\n" + + " -level1\n" + + " -level2\n" + + " -level3\n" + + " -level4\n" + + " -level5\n" + + " -level6\n" + + " -level7\n" + + " -level8\n" + + " -level9\n" + + " -level10\n" + + " +level11\n"); + } + + private void assertCallbackOnce(final TreeAction action) { + final int[] notifyCount = new int[1]; + final boolean[] done = new boolean[1]; + invokeLaterIfNeeded(new Runnable() { + public void run() { + action.run(new Runnable() { + public void run() { + notifyCount[0]++; + done[0] = true; + } + }); + } + }); + + new WaitFor(60000) { + @Override + protected boolean condition() { + return done[0] && getMyBuilder().getUi().isReady(); + } + }; + + assertTrue(done[0]); + assertEquals(1, notifyCount[0]); + } + + public void testSelectMultiple() throws Exception { + buildStructure(myRoot); + assertTree( + "+/\n"); + + select(new Object[] {new NodeElement("openapi"), new NodeElement("fabrique")}, false); + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " +[fabrique]\n" + + " +org\n" + + " +xunit\n"); + } + + public void testUnsuccessfulSelect() throws Exception { + buildStructure(myRoot); + select(new Object[] {new NodeElement("openapi"), new NodeElement("fabrique")}, false); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " +[fabrique]\n" + + " +org\n" + + " +xunit\n"); + + select(new Object[] {new NodeElement("whatever1"), new NodeElement("whatever2")}, false); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " -jetbrains\n" + + " +[fabrique]\n" + + " +org\n" + + " +xunit\n"); + } + + + public void testSelectionWhenChildMoved() throws Exception { + buildStructure(myRoot); + assertTree("+/\n"); + + final Node refactoring = myCom.getChildNode("intellij").addChild("refactoring"); + + buildNode("refactoring", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " openapi\n" + + " [refactoring]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + refactoring.delete(); + myCom.getChildNode("intellij").getChildNode("openapi").addChild("refactoring"); + + updateFromRoot(); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " -openapi\n" + + " [refactoring]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + + public void testSelectionGoesToParentWhenOnlyChildRemove() throws Exception { + buildStructure(myRoot); + buildNode("openapi", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myCom.getChildNode("intellij").getChildNode("openapi").delete(); + + updateFromRoot(); + + assertTree( + "-/\n" + + " -com\n" + + " [intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + public void testSelectionGoesToParentWhenOnlyChildMoved() throws Exception { + buildStructure(myRoot); + buildNode("openapi", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myCom.getChildNode("intellij").getChildNode("openapi").delete(); + myRoot.getChildNode("xunit").addChild("openapi"); + + updateFromRoot(); + + assertTree( + "-/\n" + + " -com\n" + + " intellij\n" + + " +jetbrains\n" + + " +org\n" + + " -xunit\n" + + " [openapi]\n" + + " runner\n"); + } + + public void testSelectionGoesToParentWhenOnlyChildMoved2() throws Exception { + buildStructure(myRoot); + buildNode("openapi", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myCom.getChildNode("intellij").getChildNode("openapi").delete(); + myRoot.getChildNode("xunit").addChild("openapi"); + + getBuilder().addSubtreeToUpdateByElement(new NodeElement("intellij")); + getBuilder().addSubtreeToUpdateByElement(new NodeElement("xunit")); + + + doAndWaitForBuilder(new Runnable() { + public void run() { + getBuilder().getUpdater().performUpdate(); + } + }); + + assertTree( + "-/\n" + + " -com\n" + + " intellij\n" + + " +jetbrains\n" + + " +org\n" + + " -xunit\n" + + " [openapi]\n" + + " runner\n"); + } + + public void testSelectionGoesToParentWhenChildrenFold() throws Exception { + buildStructure(myRoot); + buildNode("openapi", true); + + assertTree( + "-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + final DefaultMutableTreeNode node = findNode("intellij", false); + collapsePath(new TreePath(node.getPath())); + + assertTree( + "-/\n" + + " -com\n" + + " +[intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + + public void testQueryStructure() throws Exception { + buildStructure(myRoot); + + assertTree("+/\n"); + assertUpdates("/: update"); + + expand(getPath("/")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + assertUpdates("/: update getChildren\n" + + "com: update getChildren\n" + + "eclipse: update\n" + + "fabrique: update\n" + + "intellij: update\n" + + "jetbrains: update getChildren\n" + + "org: update getChildren\n" + + "runner: update\n" + + "xunit: update getChildren"); + + collapsePath(getPath("/")); + assertTree("+/\n"); + assertUpdates(""); + + expand(getPath("/")); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + assertUpdates("/: update getChildren\n" + + "com: update getChildren\n" + + "eclipse: update\n" + + "fabrique: update\n" + + "intellij: update\n" + + "jetbrains: update getChildren\n" + + "org: update getChildren\n" + + "runner: update\n" + + "xunit: update getChildren"); + + updateFromRoot(); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + assertUpdates("/: update getChildren\n" + + "com: update getChildren\n" + + "eclipse: update\n" + + "fabrique: update\n" + + "intellij: update\n" + + "jetbrains: update getChildren\n" + + "org: update getChildren\n" + + "runner: update\n" + + "xunit: update getChildren"); + + } + + public void testQueryStructureWhenExpand() throws Exception { + buildStructure(myRoot); + + assertTree("+/\n"); + assertUpdates("/: update"); + + buildNode("ide", false); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + assertUpdates("/: update getChildren\n" + + "com: update getChildren\n" + + "eclipse: update\n" + + "fabrique: update (2) getChildren\n" + + "ide: update getChildren\n" + + "intellij: update\n" + + "jetbrains: update getChildren\n" + + "org: update getChildren\n" + + "runner: update\n" + + "xunit: update getChildren"); + + } + + public void testQueryStructureIsAlwaysLeaf() throws Exception { + buildStructure(myRoot); + myStructure.addLeaf(new NodeElement("openapi")); + + buildNode("jetbrains", false); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " +fabrique\n" + + " +org\n" + + " +xunit\n"); + + assertUpdates("/: update (2) getChildren\n" + + "com: update getChildren\n" + + "eclipse: update\n" + + "fabrique: update (2) getChildren\n" + + "ide: update\n" + + "intellij: update\n" + + "jetbrains: update getChildren\n" + + "org: update getChildren\n" + + "runner: update\n" + + "xunit: update getChildren"); + + expand(getPath("fabrique")); + assertTree("-/\n" + + " +com\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + assertUpdates("ide: update getChildren"); + + + buildNode("com", false); + assertTree("-/\n" + + " -com\n" + + " +intellij\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + myElementUpdate.clear(); + + expand(getPath("intellij")); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " openapi\n" + + " -jetbrains\n" + + " -fabrique\n" + + " ide\n" + + " +org\n" + + " +xunit\n"); + + assertUpdates(""); + } + + public void testToggleIsAlwaysLeaf() throws Exception { + buildStructure(myRoot); + + buildNode("openapi", true); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myStructure.addLeaf(new NodeElement("intellij")); + + updateFrom(new NodeElement("com")); + + assertTree("-/\n" + + " -com\n" + + " [intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myStructure.removeLeaf(new NodeElement("intellij")); + updateFrom(new NodeElement("com")); + + assertTree("-/\n" + + " -com\n" + + " +[intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("intellij")); + + assertTree("-/\n" + + " -com\n" + + " -[intellij]\n" + + " openapi\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + } + + + public void testSorting() throws Exception { + buildStructure(myRoot); + assertSorted(""); + + buildNode("/", false); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + assertSorted("/\n" + + "com\n" + + "jetbrains\n" + + "org\n" + + "xunit"); + + updateFromRoot(); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + assertSorted("/\n" + + "com\n" + + "jetbrains\n" + + "org\n" + + "xunit"); + + updateFrom(new NodeElement("/"), false); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + assertSorted(""); + + + expand(getPath("com")); + assertTree("-/\n" + + " -com\n" + + " +intellij\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + assertSorted("intellij"); + } + + public void testResorting() throws Exception { + final boolean invert[] = new boolean[] {false}; + NodeDescriptor.NodeComparator c = new NodeDescriptor.NodeComparator() { + public int compare(NodeDescriptor o1, NodeDescriptor o2) { + return invert[0] ? AlphaComparator.INSTANCE.compare(o2, o1) : AlphaComparator.INSTANCE.compare(o1, o2); + } + }; + + myComparator.setDelegate(c); + + buildStructure(myRoot); + + buildNode("/", false); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + updateFromRoot(); + updateFromRoot(); + updateFromRoot(); + + assertTrue(getMyBuilder().getUi().myOwnComparatorStamp > c.getStamp()); + invert[0] = true; + c.incStamp(); + + updateFrom(new NodeElement("/"), false); + assertTree("-/\n" + + " +xunit\n" + + " +org\n" + + " +jetbrains\n" + + " +com\n"); + } + + public void testRestoreSelectionOfRemovedElement() throws Exception { + buildStructure(myRoot); + buildNode("openapi", true); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + removeFromParentButKeepRef(new NodeElement("openapi")); + + updateFromRoot(); + + assertTree("-/\n" + + " -com\n" + + " [intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + public void testElementMove1() throws Exception { + assertMove(new Runnable() { + public void run() { + getBuilder().getUpdater().addSubtreeToUpdateByElement(new NodeElement("com")); + getBuilder().getUpdater().addSubtreeToUpdateByElement(new NodeElement("jetbrains")); + } + }); + } + + public void testElementMove2() throws Exception { + assertMove(new Runnable() { + public void run() { + getBuilder().getUpdater().addSubtreeToUpdateByElement(new NodeElement("jetbrains")); + getBuilder().getUpdater().addSubtreeToUpdateByElement(new NodeElement("com")); + } + }); + } + + public void testSelectionOnDelete() throws Exception { + doTestSelectionOnDelete(false); + } + + public void testSelectionOnDeleteButKeepRef() throws Exception { + doTestSelectionOnDelete(true); + } + + private void doTestSelectionOnDelete(boolean keepRef) throws Exception { + myComparator.setDelegate(new NodeDescriptor.NodeComparator() { + public int compare(NodeDescriptor o1, NodeDescriptor o2) { + boolean isParent1 = myStructure._getChildElements(o1.getElement(), false).length > 0; + boolean isParent2 = myStructure._getChildElements(o2.getElement(), false).length > 0; + + int result = AlphaComparator.INSTANCE.compare(o1, o2); + + if (isParent1) { + result -= 1000; + } + + if (isParent2) { + result += 1000; + } + + return result; + } + }); + + buildStructure(myRoot); + myRoot.addChild("toDelete"); + + select(new NodeElement("toDelete"), false); + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n" + + " [toDelete]\n"); + + if (keepRef) { + removeFromParentButKeepRef(new NodeElement("toDelete")); + } else { + myStructure.getNodeFor(new NodeElement("toDelete")).delete(); + } + + getMyBuilder().addSubtreeToUpdateByElement(new NodeElement("/")); + + assertTree("-/\n" + + " +com\n" + + " +jetbrains\n" + + " +org\n" + + " +[xunit]\n"); + } + + public void testSelectWhenUpdatesArePending() throws Exception { + getBuilder().getUpdater().setDelay(1000); + + buildStructure(myRoot); + + buildNode("intellij", false); + select(new Object[] {new NodeElement("intellij")}, false); + assertTree("-/\n" + + " -com\n" + + " -[intellij]\n" + + " openapi\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + myIntellij.addChild("ui"); + + DefaultMutableTreeNode intellijNode = findNode("intellij", false); + assertTrue(myTree.isExpanded(new TreePath(intellijNode.getPath()))); + getMyBuilder().addSubtreeToUpdate(intellijNode); + assertFalse(getMyBuilder().getUi().isReady()); + + select(new Object[] {new NodeElement("ui")}, false); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " openapi\n" + + " [ui]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + public void testAddNewElementToLeafElementAlwaysShowPlus() throws Exception { + myAlwaysShowPlus.add(new NodeElement("openapi")); + + buildStructure(myRoot); + select(new Object[] {new NodeElement("openapi")}, false); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " +[openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("openapi")); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + myOpenApi.addChild("ui"); + + getMyBuilder().addSubtreeToUpdate(findNode("openapi", false)); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " +[openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + public void testAddNewElementToLeafElement() throws Exception { + buildStructure(myRoot); + select(new Object[] {new NodeElement("openapi")}, false); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + expand(getPath("openapi")); + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " [openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + + myOpenApi.addChild("ui"); + + getMyBuilder().addSubtreeToUpdate(findNode("openapi", false)); + + assertTree("-/\n" + + " -com\n" + + " -intellij\n" + + " +[openapi]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + } + + + private void assertMove(Runnable updateRoutine) throws Exception { + buildStructure(myRoot); + + buildNode("intellij", true); + assertTree("-/\n" + + " -com\n" + + " +[intellij]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + AbstractTreeBuilderTest.Node intellij = removeFromParentButKeepRef(new NodeElement("intellij")); + myRoot.getChildNode("jetbrains").addChild(intellij); + + updateRoutine.run(); + + assertTree("-/\n" + + " com\n" + + " -jetbrains\n" + + " +fabrique\n" + + " +[intellij]\n" + + " +org\n" + + " +xunit\n"); + + } + + + public void testChangeRootElement() throws Exception { + buildStructure(myRoot); + + select(new NodeElement("com"), false); + + assertTree("-/\n" + + " +[com]\n" + + " +jetbrains\n" + + " +org\n" + + " +xunit\n"); + + myRoot = new Node(null, "root"); + myStructure.reinitRoot(myRoot); + + myRoot.addChild("com"); + + updateFromRoot(); + assertTree("+root\n"); + } + + public void testReleaseBuilderDuringUpdate() throws Exception { + assertReleaseDuringBuilding("update", "fabrique", new Runnable() { + public void run() { + try { + select(new NodeElement("ide"), false); + } + catch (Exception e) { + myCancelRequest = e; + } + } + }); + } + + public void testReleaseBuilderDuringGetChildren() throws Exception { + assertReleaseDuringBuilding("getChildren", "fabrique", new Runnable() { + public void run() { + try { + select(new NodeElement("ide"), false); + } + catch (Exception e) { + myCancelRequest = e; + } + } + }); + } + + private void assertReleaseDuringBuilding(final String actionAction, final Object actionElement, Runnable buildAction) throws Exception { + buildStructure(myRoot); + + myElementUpdateHook = new ElementUpdateHook() { + public void onElementAction(String action, Object element) { + if (!element.toString().equals(actionElement.toString())) return; + + if (actionAction.equals(action)) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myReadyRequest = true; + Disposer.dispose(getBuilder()); + } + }); + } + } + }; + + buildAction.run(); + + boolean released = new WaitFor(5000) { + @Override + protected boolean condition() { + return getBuilder().getUi() == null; + } + }.isConditionRealized(); + + assertTrue(released); + } + + public static class SyncUpdate extends TreeUiTest { + public SyncUpdate() { + super(false, false); + } + } + + public static class Passthrough extends TreeUiTest { + public Passthrough() { + super(true); + } + + public void testSelectionGoesToParentWhenOnlyChildMoved2() throws Exception { + //todo + } + + public void testQueryStructureWhenExpand() throws Exception { + //todo + } + + + public void testElementMove1() throws Exception { + //todo + } + + @Override + public void testClear() throws Exception { + //todo + } + + @Override + public void testSelectWhenUpdatesArePending() throws Exception { + // doesn't make sense in pass-through mode + } + } + + public static class YieldingUpdate extends TreeUiTest { + public YieldingUpdate() { + super(true, false); + } + } + + public static class BgLoadingSyncUpdate extends TreeUiTest { + public BgLoadingSyncUpdate() { + super(false, true); + } + + @Override + protected int getChildrenLoadingDelay() { + return 300; + } + + @Override + protected int getNodeDescriptorUpdateDelay() { + return 300; + } + + @Override + public void testNoInfiniteSmartExpand() throws Exception { + //todo + } + } + + public static class QuickBgLoadingSyncUpdate extends TreeUiTest { + public QuickBgLoadingSyncUpdate() { + super(false, true); + } + + + @Override + protected int getNodeDescriptorUpdateDelay() { + return 300; + } + + @Override + public void testNoInfiniteSmartExpand() throws Exception { + //todo + } + + } + + public static TestSuite suite() { + TestSuite suite = new TestSuite(); + suite.addTestSuite(Passthrough.class); + suite.addTestSuite(SyncUpdate.class); + suite.addTestSuite(YieldingUpdate.class); + suite.addTestSuite(BgLoadingSyncUpdate.class); + suite.addTestSuite(QuickBgLoadingSyncUpdate.class); + return suite; + } +} diff --git a/platform/testFramework/src/com/intellij/testFramework/FlyIdeaTestCase.java b/platform/testFramework/src/com/intellij/testFramework/FlyIdeaTestCase.java new file mode 100644 index 000000000000..c6583ea0bebc --- /dev/null +++ b/platform/testFramework/src/com/intellij/testFramework/FlyIdeaTestCase.java @@ -0,0 +1,44 @@ +package com.intellij.testFramework; + +import com.intellij.mock.MockApplication; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.io.FileUtil; +import junit.framework.TestCase; + +import java.io.File; +import java.io.IOException; + +public abstract class FlyIdeaTestCase extends TestCase { + + private Disposable myRootDisposable; + private File myTempDir; + + protected void setUp() throws Exception { + MockApplication app = new MockApplication(); + myRootDisposable = Disposer.newDisposable(); + ApplicationManagerEx.setApplication(app, myRootDisposable); + + } + + public File getTempDir() throws IOException { + if (myTempDir == null) { + myTempDir = FileUtil.createTempDirectory(getName(), getClass().getName()); + } + + return myTempDir; + } + + public Disposable getRootDisposable() { + return myRootDisposable; + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (myTempDir != null) { + FileUtil.asyncDelete(myTempDir); + } + Disposer.dispose(myRootDisposable); + } +} diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index d616b7cd8ce4..e0c0b24009cf 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java @@ -16,6 +16,7 @@ package com.intellij.testFramework; import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.idea.Bombed; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; @@ -37,8 +38,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; -import java.util.Calendar; -import java.util.Date; +import java.util.*; /** * @author yole @@ -200,4 +200,76 @@ public class PlatformTestUtil { private static boolean isItMe(final String who) { return Comparing.equal(who, SystemProperties.getUserName(), false); } + + public static StringBuffer print(AbstractTreeStructure structure, + Object node, + int currentLevel, + Comparator comparator, + int maxRowCount, + char paddingChar) { + StringBuffer buffer = new StringBuffer(); + doPrint(buffer, currentLevel, node, structure, comparator, maxRowCount, 0, paddingChar); + return buffer; + } + + private static int doPrint(StringBuffer buffer, + int currentLevel, + Object node, + AbstractTreeStructure structure, + Comparator comparator, + int maxRowCount, + int currentLine, + char paddingChar) { + if (currentLine >= maxRowCount && maxRowCount != -1) return currentLine; + + StringUtil.repeatSymbol(buffer, paddingChar, currentLevel); + buffer.append(toString(node)).append("\n"); + currentLine++; + Object[] children = structure.getChildElements(node); + + if (comparator != null) { + ArrayList list = new ArrayList(Arrays.asList(children)); + Collections.sort(list, comparator); + children = list.toArray(new Object[list.size()]); + } + for (Object child : children) { + currentLine = doPrint(buffer, currentLevel + 1, child, structure, comparator, maxRowCount, currentLine, paddingChar); + } + + return currentLine; + } + + public static String print(Object[] objects) { + return print(Arrays.asList(objects)); + } + + public static String print(Collection c) { + StringBuilder result = new StringBuilder(); + for (Iterator iterator = c.iterator(); iterator.hasNext();) { + Object each = iterator.next(); + result.append(toString(each)); + if (iterator.hasNext()) { + result.append("\n"); + } + } + + return result.toString(); + } + + public static String print(ListModel model) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < model.getSize(); i++) { + result.append(toString(model.getElementAt(i))); + result.append("\n"); + } + return result.toString(); + } + + public static String print(JTree tree) { + return print(tree, false); + } + + public static void assertTreeStructureEquals(final AbstractTreeStructure treeStructure, final String expected) { + Assert.assertEquals(expected, print(treeStructure, treeStructure.getRootElement(), 0, null, -1, ' ').toString()); + } }