Public (Konstantin.Scheglov@gmail.com):
Introduces HasTreeItems interface for Tree and TreeItem, and adds UiBinder
support for building trees. Makes it possible for GWT Designer to do the same.

Reviewed: rjrjr@google.com
http://gwt-code-reviews.appspot.com/1233803

Review by: jlabanca@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9510 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HasTreeItemsParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HasTreeItemsParser.java
new file mode 100644
index 0000000..6151afa
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HasTreeItemsParser.java
@@ -0,0 +1,60 @@
+/*
+ * 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.uibinder.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.TreeItem;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.Tree} widgets.
+ */
+public class HasTreeItemsParser implements ElementParser {
+
+  static final String BAD_CHILD = "Only TreeItem or Widget subclasses are valid children";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Prepare base types.
+    JClassType itemType = writer.getOracle().findType(TreeItem.class.getName());
+    JClassType widgetType = writer.getOracle().findType(Widget.class.getName());
+
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      JClassType childType = writer.findFieldType(child);
+
+      // TreeItem+
+      if (itemType.isAssignableFrom(childType)) {
+        String childFieldName = writer.parseElementToField(child);
+        writer.addStatement("%1$s.addItem(%2$s);", fieldName, childFieldName);
+        continue;
+      }
+
+      // Widget+
+      if (widgetType.isAssignableFrom(childType)) {
+        String childFieldName = writer.parseElementToField(child);
+        writer.addStatement("%1$s.addItem(%2$s);", fieldName, childFieldName);
+        continue;
+      }
+
+      // Fail
+      writer.die(child, BAD_CHILD);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index a7589b5..e6be10a 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -978,6 +978,7 @@
     addWidgetParser("UIObject");
     addWidgetParser("HasText");
     addWidgetParser("HasHTML");
+    addWidgetParser("HasTreeItems");
     addWidgetParser("HasWidgets");
     addWidgetParser("HTMLPanel");
     addWidgetParser("AbsolutePanel");
diff --git a/user/src/com/google/gwt/user/client/ui/HasTreeItems.java b/user/src/com/google/gwt/user/client/ui/HasTreeItems.java
new file mode 100644
index 0000000..d8caf16
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/HasTreeItems.java
@@ -0,0 +1,83 @@
+/*
+ * 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.client.ui;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * A widget that implements this interface contains
+ * {@link com.google.gwt.user.client.ui.TreeItem items} and can operate them.
+ */
+public interface HasTreeItems {
+  
+  /**
+   * Adds a simple tree item containing the specified html.
+   * 
+   * @param itemHtml the html of the item to be added
+   * @return the item that was added
+   */
+  TreeItem addItem(SafeHtml itemHtml);
+
+  /**
+   * Adds an tree item.
+   * 
+   * @param item the item to be added
+   */
+  void addItem(TreeItem item);
+  
+  /**
+   * Adds an item wrapped by specified {@link IsTreeItem}.
+   * 
+   * @param isItem the wrapper of item to be added
+   */
+  void addItem(IsTreeItem isItem);
+
+  /**
+   * Adds a new tree item containing the specified widget.
+   * 
+   * @param widget the widget to be added
+   * @return the new item
+   */
+  TreeItem addItem(Widget widget);
+  
+  /**
+   * Adds a simple tree item containing the specified text.
+   * 
+   * @param itemText the text of the item to be added
+   * @return the item that was added
+   */
+  TreeItem addTextItem(String itemText);
+
+  /**
+   * Removes an item.
+   * 
+   * @param item the item to be removed
+   */
+  void removeItem(TreeItem item);
+  
+  /**
+   * Removes an item.
+   * 
+   * @param isItem the wrapper of item to be removed
+   */
+  void removeItem(IsTreeItem isItem);
+
+  /**
+   * Removes all items.
+   */
+  void removeItems();
+
+}
diff --git a/user/src/com/google/gwt/user/client/ui/IsTreeItem.java b/user/src/com/google/gwt/user/client/ui/IsTreeItem.java
new file mode 100644
index 0000000..ec1041a
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/IsTreeItem.java
@@ -0,0 +1,29 @@
+/*
+ * 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.client.ui;
+
+/**
+ * Extended by objects which have underlying {@link TreeItem}.
+ * Provides access to that item, if it exists, without compromising the
+ * ability to provide a mock object instance in JRE unit tests.
+ */
+public interface IsTreeItem {
+
+  /**
+   * Returns the {@link TreeItem} aspect of the receiver.
+   */
+  TreeItem asTreeItem();
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index b468940..5745374 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -89,10 +89,16 @@
  * </p>
  */
 @SuppressWarnings("deprecation")
-public class Tree extends Widget implements HasWidgets, SourcesTreeEvents,
-    HasFocus, HasAnimation, HasAllKeyHandlers, HasAllFocusHandlers,
-    HasSelectionHandlers<TreeItem>, HasOpenHandlers<TreeItem>,
-    HasCloseHandlers<TreeItem>, SourcesMouseEvents, HasAllMouseHandlers {
+public class Tree extends Widget implements HasTreeItems, HasWidgets, 
+    SourcesTreeEvents, HasFocus, HasAnimation, HasAllKeyHandlers,
+    HasAllFocusHandlers, HasSelectionHandlers<TreeItem>,
+    HasOpenHandlers<TreeItem>, HasCloseHandlers<TreeItem>, SourcesMouseEvents,
+    HasAllMouseHandlers {
+  /*
+   * For compatibility with UiBinder interface HasTreeItems should be declared
+   * before HasWidgets, so that corresponding parser will run first and add
+   * TreeItem children as items, not as widgets.
+   */
 
   /**
    * A ClientBundle that provides images for this widget.
@@ -317,18 +323,15 @@
   public void addFocusListener(FocusListener listener) {
     ListenerWrapper.WrappedFocusListener.add(this, listener);
   }
-
+  
   /**
-   * Adds a simple tree item containing the specified text.
+   * Adds a simple tree item containing the specified html.
    * 
-   * @param itemText the text of the item to be added
+   * @param itemHtml the text of the item to be added
    * @return the item that was added
    */
-  public TreeItem addItem(String itemText) {
-    TreeItem ret = new TreeItem(itemText);
-    addItem(ret);
-
-    return ret;
+  public TreeItem addItem(String itemHtml) {
+    return root.addItem(itemHtml);
   }
 
   /**
@@ -349,6 +352,15 @@
   public void addItem(TreeItem item) {
     root.addItem(item);
   }
+  
+  /**
+   * Adds an item to the root level of this tree.
+   * 
+   * @param isItem the wrapper of item to be added
+   */
+  public void addItem(IsTreeItem isItem) {
+    root.addItem(isItem);
+  }
 
   /**
    * Adds a new tree item containing the specified widget.
@@ -423,6 +435,16 @@
       SelectionHandler<TreeItem> handler) {
     return addHandler(handler, SelectionEvent.getType());
   }
+  
+  /**
+   * Adds a simple tree item containing the specified text.
+   * 
+   * @param itemText the text of the item to be added
+   * @return the item that was added
+   */
+  public TreeItem addTextItem(String itemText) {
+    return root.addTextItem(itemText);
+  }
 
   /**
    * @deprecated Use {@link #addSelectionHandler}, {@link #addOpenHandler}, and
@@ -675,6 +697,18 @@
   public void removeItem(TreeItem item) {
     root.removeItem(item);
   }
+  
+  /**
+   * Removes an item from the root level of this tree.
+   * 
+   * @param isItem the wrapper of item to be removed
+   */
+  public void removeItem(IsTreeItem isItem) {
+    if (isItem != null) { 
+      TreeItem item = isItem.asTreeItem();
+      removeItem(item);
+    }
+  }
 
   /**
    * Removes all items from the root level of this tree.
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index 1244015..8ed1d46 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -39,7 +39,13 @@
  * {@example com.google.gwt.examples.TreeExample}
  * </p>
  */
-public class TreeItem extends UIObject implements HasHTML, HasSafeHtml {
+public class TreeItem extends UIObject implements IsTreeItem, HasTreeItems,
+    HasHTML, HasSafeHtml {
+  /*
+   * For compatibility with UiBinder interface HasTreeItems should be declared
+   * before HasHTML, so that children items and widgets are processed before
+   * interpreting HTML. 
+   */
 
   /**
    * The margin applied to child items.
@@ -326,13 +332,13 @@
   }
 
   /**
-   * Adds a child tree item containing the specified text.
+   * Adds a child tree item containing the specified html.
    * 
-   * @param itemText the text to be added
+   * @param itemHtml the text to be added
    * @return the item that was added
    */
-  public TreeItem addItem(String itemText) {
-    TreeItem ret = new TreeItem(itemText);
+  public TreeItem addItem(String itemHtml) {
+    TreeItem ret = new TreeItem(itemHtml);
     addItem(ret);
     return ret;
   }
@@ -360,6 +366,16 @@
     maybeRemoveItemFromParent(item);
     insertItem(getChildCount(), item);
   }
+  
+  /**
+   * Adds another item as a child to this one.
+   * 
+   * @param isItem the wrapper of item to be added
+   */
+  public void addItem(IsTreeItem isItem) {
+    TreeItem item = isItem.asTreeItem();
+    addItem(item);
+  }
 
   /**
    * Adds a child tree item containing the specified widget.
@@ -372,6 +388,23 @@
     addItem(ret);
     return ret;
   }
+  
+  /**
+   * Adds a child tree item containing the specified text.
+   * 
+   * @param itemText the text of the item to be added
+   * @return the item that was added
+   */
+  public TreeItem addTextItem(String itemText) {
+    TreeItem ret = new TreeItem();
+    ret.setText(itemText);
+    addItem(ret);
+    return ret;
+  }
+  
+  public TreeItem asTreeItem() {
+    return this;
+  }
 
   /**
    * Gets the child at the specified index.
@@ -597,7 +630,6 @@
    * 
    * @param item the item to be removed
    */
-
   public void removeItem(TreeItem item) {
     // Validate.
     if (children == null || !children.contains(item)) {
@@ -623,6 +655,18 @@
       updateState(false, false);
     }
   }
+  
+  /**
+   * Removes one of this item's children.
+   * 
+   * @param isItem the wrapper of item to be removed
+   */
+  public void removeItem(IsTreeItem isItem) {
+    if (isItem != null) { 
+      TreeItem item = isItem.asTreeItem();
+      removeItem(item);
+    }
+  }
 
   /**
    * Removes all of this item's children.
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index 5436f36..7546a3f 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -42,6 +42,7 @@
 import com.google.gwt.uibinder.elementparsers.StackPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.TabLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.TabPanelParserTest;
+import com.google.gwt.uibinder.elementparsers.HasTreeItemsParserTest;
 import com.google.gwt.uibinder.elementparsers.UIObjectParserTest;
 import com.google.gwt.uibinder.rebind.DesignTimeUtilsTest;
 import com.google.gwt.uibinder.rebind.FieldWriterOfGeneratedCssResourceTest;
@@ -106,6 +107,7 @@
     suite.addTestSuite(StackPanelParserTest.class);
     suite.addTestSuite(TabLayoutPanelParserTest.class);
     suite.addTestSuite(TabPanelParserTest.class);
+    suite.addTestSuite(HasTreeItemsParserTest.class);
     suite.addTestSuite(UIObjectParserTest.class);
 
     return suite;
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/HasTreeItemsParserTest.java b/user/test/com/google/gwt/uibinder/elementparsers/HasTreeItemsParserTest.java
new file mode 100644
index 0000000..10c311f
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/elementparsers/HasTreeItemsParserTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.uibinder.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import junit.framework.TestCase;
+
+import java.util.Iterator;
+
+/**
+ * Test for {@link HasTreeItemsParser}.
+ */
+public class HasTreeItemsParserTest extends TestCase {
+
+  private static final String PARSED_TYPE = "com.google.gwt.user.client.ui.Tree";
+
+  private HasTreeItemsParser parser;
+  private ElementParserTester tester;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    parser = new HasTreeItemsParser();
+    tester = new ElementParserTester(PARSED_TYPE, parser);
+  }
+
+  public void testBadChild_namespace() throws Exception {
+    checkBadLine("<ui:blah/>", HasTreeItemsParser.BAD_CHILD);
+  }
+
+  public void testBadChild_name() throws Exception {
+    checkBadLine("<g:MenuItem/>", HasTreeItemsParser.BAD_CHILD);
+  }
+
+  private void checkBadLine(String badLine, String expectedDied)
+      throws Exception {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:Tree>");
+    b.append("  " + badLine);
+    b.append("</g:Tree>");
+    // parse failed
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      String died = tester.logger.died;
+      assertTrue(died, died.contains(expectedDied));
+    }
+  }
+
+  public void test_TreeItem() throws Exception {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:Tree>");
+    b.append("  <g:TreeItem text='1'/>");
+    b.append("  <g:TreeItem text='2'/>");
+    b.append("</g:Tree>");
+    // parse
+    tester.parse(b.toString());
+    assertStatements("fieldName.addItem(<g:TreeItem text='1'>);",
+        "fieldName.addItem(<g:TreeItem text='2'>);");
+  }
+
+  public void test_Widget() throws Exception {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:Tree>");
+    b.append("  <g:Button text='1'/>");
+    b.append("  <g:Button text='2'/>");
+    b.append("</g:Tree>");
+    // parse
+    tester.parse(b.toString());
+    assertStatements("fieldName.addItem(<g:Button text='1'>);",
+        "fieldName.addItem(<g:Button text='2'>);");
+  }
+
+  public void test_WidgetItemMix() throws Exception {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:Tree>");
+    b.append("  <g:Button text='1'/>");
+    b.append("  <g:TreeItem text='2'/>");
+    b.append("  <g:Button text='3'/>");
+    b.append("  <g:TreeItem text='4'/>");
+    b.append("</g:Tree>");
+    // parse
+    tester.parse(b.toString());
+    assertStatements("fieldName.addItem(<g:Button text='1'>);",
+        "fieldName.addItem(<g:TreeItem text='2'>);",
+        "fieldName.addItem(<g:Button text='3'>);",
+        "fieldName.addItem(<g:TreeItem text='4'>);");
+  }
+
+  private void assertStatements(String... expected) {
+    Iterator<String> i = tester.writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(tester.logger.died);
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
index ad1280a..a606cc9 100644
--- a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -516,6 +516,28 @@
       return code;
     }
   };
+  public static final MockJavaResource TREE = new MockJavaResource(
+      "com.google.gwt.user.client.ui.Tree") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class Tree extends Widget {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource TREE_ITEM = new MockJavaResource(
+      "com.google.gwt.user.client.ui.TreeItem") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class TreeItem extends UIObject {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
   public static final MockJavaResource TIME_ZONE = new MockJavaResource(
       "com.google.gwt.i18n.client.TimeZone") {
     @Override
@@ -634,6 +656,8 @@
     rtn.add(TAB_PANEL);
     rtn.add(TEXT_BOX_BASE);
     rtn.add(TIME_ZONE);
+    rtn.add(TREE);
+    rtn.add(TREE_ITEM);
     rtn.add(UI_OBJECT);
     rtn.add(UI_BINDER);
     rtn.add(UI_FACTORY);
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
index 74426a9..ef94b3c 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
@@ -38,6 +38,8 @@
 import com.google.gwt.user.client.ui.NumberLabel;
 import com.google.gwt.user.client.ui.RadioButton;
 import com.google.gwt.user.client.ui.StackPanel;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.TreeItem;
 import com.google.gwt.user.client.ui.ValueLabel;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -471,6 +473,20 @@
     NamedFrame p = widgetUi.myNamedFrame;
     assertNotNull("NamedFrame exists", p);
   }
+  
+  public void testTree() {
+    Tree tree = widgetUi.myTree;
+    TreeItem complexItem = widgetUi.myTreeItemC;
+    // top level items
+    assertEquals(3, tree.getItemCount());
+    assertSame(widgetUi.myTreeItemA, tree.getItem(0));
+    assertSame(widgetUi.myTreeWidgetB, tree.getItem(1).getWidget());
+    assertSame(complexItem, tree.getItem(2));
+    // complex item
+    assertSame(2, complexItem.getChildCount());
+    assertSame(widgetUi.myTreeItemCA, complexItem.getChild(0));
+    assertSame(widgetUi.myTreeWidgetCB, complexItem.getChild(1).getWidget());
+  }
 
   public void testDateLabel() {
     DateLabel p = widgetUi.myDateLabel;
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
index 9cd127e..8e099e0 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
@@ -54,6 +54,7 @@
 import com.google.gwt.user.client.ui.StackPanel;
 import com.google.gwt.user.client.ui.ToggleButton;
 import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.TreeItem;
 import com.google.gwt.user.client.ui.ValueLabel;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -108,6 +109,11 @@
   @UiField DisclosurePanel myDisclosurePanel;
   @UiField Widget myDisclosurePanelItem;
   @UiField Tree myTree;
+  @UiField TreeItem myTreeItemA;
+  @UiField Widget myTreeWidgetB;
+  @UiField TreeItem myTreeItemC;
+  @UiField TreeItem myTreeItemCA;
+  @UiField Widget myTreeWidgetCB;
   @UiField Element nonStandardElement;
   @SuppressWarnings("deprecation")
   @UiField com.google.gwt.user.client.ui.DockPanel root;
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
index 08a6b83..660cad8 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
@@ -290,7 +290,14 @@
           </gwt:MenuItem>
         </gwt:MenuBar>
         </div>
-        <gwt:Tree ui:field='myTree' width="100px" />
+        <gwt:Tree ui:field="myTree" width="100px">
+          <gwt:TreeItem ui:field="myTreeItemA"/>
+          <gwt:Button ui:field="myTreeWidgetB"/>
+          <gwt:TreeItem ui:field="myTreeItemC">
+            <gwt:TreeItem ui:field="myTreeItemCA"/>
+            <gwt:Button ui:field="myTreeWidgetCB"/>
+          </gwt:TreeItem>
+        </gwt:Tree>
 
       <p>...TextBoxes...</p>
 
diff --git a/user/test/com/google/gwt/user/client/ui/TreeItemTest.java b/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
index da6a325..be29293 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
@@ -46,12 +46,39 @@
     assertEquals(b, item.getChild(0));
     assertEquals(a, item.getChild(1));
   }
+  
+  /**
+   * Test for {@link TreeItem#addItem(IsTreeItem)}.
+   */
+  public void testAddItemIsTreeItem() {
+    TreeItem root = new TreeItem("foo");
+    TreeItem item = new TreeItem("hello");
+    root.addItem((IsTreeItem) item);
+    assertEquals(1, root.getChildCount());
+    assertSame(item, root.getChild(0));
+  }
 
   public void testAddItemSafeHtml() {
     TreeItem item = new TreeItem("foo");
     TreeItem child = item.addItem(SafeHtmlUtils.fromSafeConstant(html));
     assertEquals(html, child.getHTML().toLowerCase());
   }
+  
+  /**
+   * Test for {@link Tree#addTextItem(String)}.
+   */
+  public void testAddTextItem() {
+    TreeItem root = new TreeItem("foo");
+    String text = "Some<br>text";
+    TreeItem item = root.addTextItem(text);
+    assertEquals(text, item.getText());
+    assertEquals("Some&lt;br&gt;text", item.getHTML());
+  }
+  
+  public void testAsTreeItem() {
+    TreeItem item = new TreeItem("foo");
+    assertSame(item, item.asTreeItem());
+  }
 
   public void testInsert() {
     TreeItem item = new TreeItem();
@@ -135,6 +162,41 @@
     TreeItem child = item.insertItem(0, SafeHtmlUtils.fromSafeConstant(html));
     assertEquals(html, child.getHTML().toLowerCase());
   }
+  
+  /**
+   * Test for {@link TreeItem#removeItem(IsTreeItem)}.
+   */
+  public void testRemoveIsTreeItem() {
+    TreeItem root = new TreeItem("root");
+    TreeItem itemA = root.addItem("a");
+    TreeItem itemB = root.addItem("b");
+    // initial state
+    assertEquals(2, root.getChildCount());
+    assertSame(itemA, root.getChild(0));
+    assertSame(itemB, root.getChild(1));
+    // remove "itemA" as wrapper
+    root.removeItem((IsTreeItem) itemA);
+    assertEquals(1, root.getChildCount());
+    assertSame(itemB, root.getChild(0));
+    // ignore null
+    root.removeItem((IsTreeItem) null);
+  }
+  
+  /**
+   * Test for {@link TreeItem#removeItems()}.
+   */
+  public void testRemoveItems() {
+    TreeItem root = new TreeItem("root");
+    TreeItem itemA = root.addItem("a");
+    TreeItem itemB = root.addItem("b");
+    // initial state
+    assertEquals(2, root.getChildCount());
+    assertSame(itemA, root.getChild(0));
+    assertSame(itemB, root.getChild(1));
+    // do remove
+    root.removeItems();
+    assertEquals(0, root.getChildCount());
+  }
 
   public void testSafeHtmlConstructor() {
     TreeItem item = new TreeItem(SafeHtmlUtils.fromSafeConstant(html));
diff --git a/user/test/com/google/gwt/user/client/ui/TreeTest.java b/user/test/com/google/gwt/user/client/ui/TreeTest.java
index e283fb8..cd1d8a5 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeTest.java
@@ -39,12 +39,34 @@
   public String getModuleName() {
     return "com.google.gwt.user.DebugTest";
   }
+  
+  /**
+   * Test for {@link Tree#addItem(IsTreeItem)}.
+   */
+  public void testAddItemIsTreeItem() {
+    Tree t = new Tree();
+    TreeItem item = new TreeItem("hello");
+    t.addItem((IsTreeItem) item);
+    assertEquals(1, t.getItemCount());
+    assertSame(item, t.getItem(0));
+  }
 
   public void testAddItemSafeHtml() {
     Tree t = new Tree();
     TreeItem item = t.addItem(SafeHtmlUtils.fromSafeConstant(html));
     assertEquals(html, item.getHTML().toLowerCase());
   }
+  
+  /**
+   * Test for {@link Tree#addTextItem(String)}.
+   */
+  public void testAddTextItem() {
+    Tree t = new Tree();
+    String text = "Some<br>text";
+    TreeItem item = t.addTextItem(text);
+    assertEquals(text, item.getText());
+    assertEquals("Some&lt;br&gt;text", item.getHTML());
+  }
 
   public void testAttachDetachOrder() {
     HasWidgetsTester.testAll(new Tree(), new Adder(), true);
@@ -184,6 +206,41 @@
     Iterator<TreeItem> iter2 = t.treeItemIterator();
     assertFalse(iter2.hasNext());
   }
+  
+  /**
+   * Test for {@link Tree#removeItem(IsTreeItem)}.
+   */
+  public void testRemoveIsTreeItem() {
+    Tree t = new Tree();
+    TreeItem itemA = t.addItem("a");
+    TreeItem itemB = t.addItem("b");
+    // initial state
+    assertEquals(2, t.getItemCount());
+    assertSame(itemA, t.getItem(0));
+    assertSame(itemB, t.getItem(1));
+    // remove "itemA" as wrapper
+    t.removeItem((IsTreeItem) itemA);
+    assertEquals(1, t.getItemCount());
+    assertSame(itemB, t.getItem(0));
+    // ignore null
+    t.removeItem((IsTreeItem) null);
+  }
+  
+  /**
+   * Test for {@link Tree#removeItems()}.
+   */
+  public void testRemoveItems() {
+    Tree t = new Tree();
+    TreeItem itemA = t.addItem("a");
+    TreeItem itemB = t.addItem("b");
+    // initial state
+    assertEquals(2, t.getItemCount());
+    assertSame(itemA, t.getItem(0));
+    assertSame(itemB, t.getItem(1));
+    // do remove
+    t.removeItems();
+    assertEquals(0, t.getItemCount());
+  }
 
   public void testRootAdd() {
     Tree t = new Tree();