Adds ability to include SafeHtml objects in dom based UI's if the lazy
widget option is being used (this is the only way that the setters
will work correctly).

Adds missing integration test for @UiChild, covering bugs exposed by
the first couple of shots at this change. Tidies the UiBinder test
suites in the process.

UiChildParser is now much stricter and more thoroughly unit tested,
checking for type consistency and disallowing garbage attributes on
child elements.

UiBinderWriter.isWidget now really means just
that. UiBinderWriter.isImportedElement does what isWidget used to do.

Also fixes some warnings in UiBinderWriter.

Restores r10107 (which restored r10100)

Review at http://gwt-code-reviews.appspot.com/1421811

Review by: unnurg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10137 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 2153f51..63ecdd5 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -42,6 +42,16 @@
       return code;
     }
   };
+  public static final MockJavaResource BOOLEAN = new MockJavaResource("java.lang.Boolean") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public class Boolean {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
   public static final MockJavaResource BYTE = new MockJavaResource(
       "java.lang.Byte") {
     @Override
@@ -401,7 +411,7 @@
 
   public static MockJavaResource[] getStandardResources() {
     return new MockJavaResource[]{
-        ANNOTATION, BYTE, CHARACTER, CLASS, CLASS_NOT_FOUND_EXCEPTION,
+        ANNOTATION, BYTE, BOOLEAN, CHARACTER, CLASS, CLASS_NOT_FOUND_EXCEPTION,
         COLLECTION, DOUBLE, ENUM, EXCEPTION, ERROR, FLOAT, INTEGER,
         IS_SERIALIZABLE, JAVASCRIPTOBJECT, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR,
         NUMBER, OBJECT, SERIALIZABLE, SHORT, STRING, STRING_BUILDER,
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
index 562c11e..4e74f03 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
@@ -16,6 +16,7 @@
 package com.google.gwt.uibinder.elementparsers;
 
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
@@ -81,8 +82,13 @@
 
   public String interpretElement(XMLElement elem)
       throws UnableToCompleteException {
-    if (writer.isWidgetElement(elem)) {
-      writer.die(elem, "Found widget in an HTML context");
+    if (writer.useLazyWidgetBuilders() &&
+        writer.isElementAssignableTo(elem, SafeHtml.class)) {
+      String childFieldName = writer.parseElementToField(elem);
+      return writer.tokenForSafeHtmlExpression(childFieldName);
+    }
+    if (writer.isImportedElement(elem)) {
+      writer.die(elem, "Not allowed in an HTML context");
     }
     return pipe.interpretElement(elem);
   }
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java b/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java
index 6599445..21c5833 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java
@@ -96,6 +96,10 @@
     numCallsToChildMethod.put(tag, priorCalls + 1);
   }
 
+  private JClassType getFirstParamType(JMethod method) {
+    return method.getParameters()[0].getType().isClassOrInterface();
+  }
+
   /**
    * Process a child element that should be added using a 
    * {@link com.google.gwt.uibinder.client.UiChild UiChild} method.
@@ -113,8 +117,15 @@
     }
     XMLElement toAdd = children.next();
 
-    if (!writer.isWidgetElement(toAdd)) {
-      writer.die(child, "Expecting only widgets in %s", child);
+    if (!writer.isImportedElement(toAdd)) {
+      writer.die(child, "Expected child from a urn:import namespace, found %s",
+          toAdd);
+    }
+    
+    JClassType paramClass = getFirstParamType(method);
+    if (!writer.isElementAssignableTo(toAdd, paramClass)) {
+      writer.die(child, "Expected child of type %s in %s, found %s", 
+          paramClass.getSimpleSourceName(), child, toAdd);
     }
 
     // Make sure that there is only one element per tag.
@@ -143,10 +154,9 @@
   }
 
   /**
-   * Go through all of the given method's required attributes and consume them
-   * from the given element. Any attributes not required by the method are left
-   * untouched. If a parameter is not present in the element, it will be passed
-   * null.
+   * Go through all of the given method's required parameters and consume them
+   * from the given element's attributes. If a parameter is not present in the
+   * element, it will be passed null. Unexpected attributes are an error.
    * 
    * @param element The element to find the necessary attributes for the
    *          parameters to the method.
@@ -172,6 +182,10 @@
           defaultValue, param.getType());
       args[index] = value;
     }
+    
+    if (element.getAttributeCount() > 0) {
+      writer.die(element, "Unexpected attributes");
+    }
     return args;
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index bdbb66d..5af8f3a 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -174,8 +174,7 @@
     return openPlaceholder;
   }
 
-  private void genSetWidgetTextCall(String idHolder, String childField)
-      throws UnableToCompleteException {
+  private void genSetWidgetTextCall(String idHolder, String childField) {
 
     if (uiWriter.useLazyWidgetBuilders()) {
       if (idIsHasText.contains(idHolder)) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
index 83ae24e..c7199e3 100644
--- a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
@@ -220,8 +220,7 @@
    * @param objectName the name of the object we want to tie the handler
    */
   void writeAddHandler(IndentedWriter writer, FieldManager fieldManager,
-      String handlerVarName, String addHandlerMethodName, String objectName)
-      throws UnableToCompleteException {
+      String handlerVarName, String addHandlerMethodName, String objectName) {
     if (useLazyWidgetBuilders) {
       fieldManager.require(objectName).addStatement("%1$s.%2$s(%3$s);",
           objectName, addHandlerMethodName, handlerVarName);
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 8e32ab0..7c14f42 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dom.client.TagName;
 import com.google.gwt.resources.client.ClientBundle;
@@ -39,6 +41,7 @@
 import com.google.gwt.uibinder.rebind.model.OwnerClass;
 import com.google.gwt.uibinder.rebind.model.OwnerField;
 import com.google.gwt.user.client.ui.Attachable;
+import com.google.gwt.user.client.ui.IsWidget;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -582,7 +585,7 @@
       throws UnableToCompleteException {
     String tagName = elem.getLocalName();
 
-    if (!isWidgetElement(elem)) {
+    if (!isImportedElement(elem)) {
       return findDomElementTypeForTag(tagName);
     }
 
@@ -698,6 +701,51 @@
     return uri != null && UiBinderGenerator.BINDER_URI.equals(uri);
   }
 
+  public boolean isElementAssignableTo(XMLElement elem, Class<?> possibleSuperclass)
+      throws UnableToCompleteException {
+    JClassType classType = oracle.findType(possibleSuperclass.getCanonicalName());
+    return isElementAssignableTo(elem, classType);
+  }
+
+  public boolean isElementAssignableTo(XMLElement elem, JClassType possibleSupertype)
+      throws UnableToCompleteException {
+    /*
+     * Things like <W extends IsWidget & IsPlaid> 
+     */
+    JTypeParameter typeParameter = possibleSupertype.isTypeParameter();
+    if (typeParameter != null) {
+      JClassType[] bounds = typeParameter.getBounds();
+      for (JClassType bound : bounds) {
+        if (!isElementAssignableTo(elem, bound)) {
+          return false;
+        }
+      }
+      return true;
+    }
+    
+    /*
+     * Binder fields are always declared raw, so we're cheating if the
+     * user is playing with parameterized types. We're happy enough if the
+     * raw types match, and rely on them to make sure the specific types
+     * really do work.
+     */
+    JParameterizedType parameterized = possibleSupertype.isParameterized();
+    if (parameterized != null) {
+      return isElementAssignableTo(elem, parameterized.getRawType());
+    }
+
+    JClassType fieldtype = findFieldType(elem);
+    if (fieldtype == null) {
+      return false;
+    }
+    return fieldtype.isAssignableTo(possibleSupertype);
+  }
+
+  public boolean isImportedElement(XMLElement elem) {
+    String uri = elem.getNamespaceUri();
+    return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);  
+  }
+
   /**
    * Checks whether the given owner field name is a LazyDomElement or not.
    */
@@ -710,9 +758,8 @@
     return lazyDomElementClass.isAssignableFrom(ownerField.getType().getRawType());
   }
 
-  public boolean isWidgetElement(XMLElement elem) {
-    String uri = elem.getNamespaceUri();
-    return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);
+  public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
+    return isElementAssignableTo(elem, IsWidget.class);
   }
 
   /**
@@ -807,32 +854,29 @@
    * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
    * keep the expression from being escaped by the SafeHtml template.
    *
-   * @param expression
+   * @param expression must resolve to trusted HTML string
    */
   public String tokenForSafeConstant(String expression) {
     if (!useSafeHtmlTemplates) {
       return tokenForStringExpression(expression);
     }
 
-    String token =  tokenator.nextToken("SafeHtmlUtils.fromSafeConstant(" +
-        expression + ")");
-    htmlTemplates.noteSafeConstant("SafeHtmlUtils.fromSafeConstant(" +
-        expression + ")");
-    return token;
+    expression = "SafeHtmlUtils.fromSafeConstant(" + expression + ")";
+    htmlTemplates.noteSafeConstant(expression);
+    return tokenator.nextToken(expression);
   }
 
   /**
-   * Like {@link #tokenForStringExpression}, but used for runtime {@link SafeHtml}
-   * instances.
-   *
-   * @param expression
+   * Like {@link #tokenForStringExpression}, but used for runtime
+   * {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} instances.
+   * 
+   * @param expression must resolve to SafeHtml object
    */
   public String tokenForSafeHtmlExpression(String expression) {
     if (!useSafeHtmlTemplates) {
       return tokenForStringExpression(expression + ".asString()");
     }
 
-    String token = tokenator.nextToken(expression);
     htmlTemplates.noteSafeConstant(expression);
     return tokenator.nextToken(expression);
   }
@@ -845,7 +889,7 @@
    * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
    * other runtime expression in the string.
    *
-   * @param expression
+   * @param expression must resolve to String
    */
   public String tokenForStringExpression(String expression) {
     return tokenator.nextToken(("\" + " + expression + " + \""));
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
index 00b16fe..daa422c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
@@ -311,6 +311,15 @@
                   + "with \"add\".");
             }
           }
+          JParameter[] parameters = method.getParameters();
+          if (parameters.length == 0) {
+            logger.die("%s must take at least one Object argument", method.getName());
+          }
+          JType type = parameters[0].getType();
+          if (type.isClassOrInterface() == null) {
+            logger.die("%s first parameter must be an object type, found %s", 
+                method.getName(), type.getQualifiedSourceName());
+          }
           uiChildren.put(tag, Pair.create(method, limit));
         }
       }
diff --git a/user/test/com/google/gwt/uibinder/All.java b/user/test/com/google/gwt/uibinder/All.java
index ab7152f..562afb7 100644
--- a/user/test/com/google/gwt/uibinder/All.java
+++ b/user/test/com/google/gwt/uibinder/All.java
@@ -32,7 +32,8 @@
     GWTTestSuite suite = new GWTTestSuite("All UiBinder tests");
 
     suite.addTest(UiBinderJreSuite.suite());
-    suite.addTest(UiBinderGwtSuite.suite());
+    suite.addTest(UiBinderSuite.suite());
+    suite.addTest(LazyWidgetBuilderSuite.suite());
 
     return suite;
   }
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/LazyWidgetBuilderSuite.java
similarity index 60%
copy from user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
copy to user/test/com/google/gwt/uibinder/LazyWidgetBuilderSuite.java
index e251075..6c69e98 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
+++ b/user/test/com/google/gwt/uibinder/LazyWidgetBuilderSuite.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -16,29 +16,23 @@
 package com.google.gwt.uibinder;
 
 import com.google.gwt.junit.tools.GWTTestSuite;
-import com.google.gwt.uibinder.client.UiBinderUtilTest;
-import com.google.gwt.uibinder.test.client.InnerWidgetTest;
-import com.google.gwt.uibinder.test.client.TestParameterizedWidgets;
-import com.google.gwt.uibinder.test.client.UiBinderTest;
+import com.google.gwt.uibinder.test.client.SafeHtmlAsComponentsTest;
 
 import junit.framework.Test;
 
 /**
  * Test suite for UiBinder GWTTestCases.
  */
-public class UiBinderGwtSuite {
+public class LazyWidgetBuilderSuite {
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite(
-        "Test suite for UiBinder GWTTestCases");
+        "Tests that rely on the useLazyWidgetBuilders switch");
 
-    suite.addTestSuite(UiBinderTest.class);
-    suite.addTestSuite(UiBinderUtilTest.class);
-    suite.addTestSuite(TestParameterizedWidgets.class);
-    suite.addTestSuite(InnerWidgetTest.class);
+    suite.addTestSuite(SafeHtmlAsComponentsTest.class);
 
     return suite;
   }
 
-  private UiBinderGwtSuite() {
+  private LazyWidgetBuilderSuite() {
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index 9db81ab..d6e2dc0 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -31,6 +31,7 @@
 import com.google.gwt.uibinder.elementparsers.DisclosurePanelParserTest;
 import com.google.gwt.uibinder.elementparsers.DockLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.GridParserTest;
+import com.google.gwt.uibinder.elementparsers.HasTreeItemsParserTest;
 import com.google.gwt.uibinder.elementparsers.ImageParserTest;
 import com.google.gwt.uibinder.elementparsers.IsEmptyParserTest;
 import com.google.gwt.uibinder.elementparsers.LayoutPanelParserTest;
@@ -42,8 +43,8 @@
 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.elementparsers.UiChildParserTest;
 import com.google.gwt.uibinder.rebind.DesignTimeUtilsTest;
 import com.google.gwt.uibinder.rebind.FieldWriterOfExistingTypeTest;
 import com.google.gwt.uibinder.rebind.FieldWriterOfGeneratedCssResourceTest;
@@ -102,6 +103,7 @@
     suite.addTestSuite(DisclosurePanelParserTest.class);
     suite.addTestSuite(DockLayoutPanelParserTest.class);
     suite.addTestSuite(GridParserTest.class);
+    suite.addTestSuite(HasTreeItemsParserTest.class);
     suite.addTestSuite(ImageParserTest.class);
     suite.addTestSuite(IsEmptyParserTest.class);
     suite.addTestSuite(LayoutPanelParserTest.class);
@@ -113,7 +115,7 @@
     suite.addTestSuite(StackPanelParserTest.class);
     suite.addTestSuite(TabLayoutPanelParserTest.class);
     suite.addTestSuite(TabPanelParserTest.class);
-    suite.addTestSuite(HasTreeItemsParserTest.class);
+    suite.addTestSuite(UiChildParserTest.class);
     suite.addTestSuite(UIObjectParserTest.class);
 
     return suite;
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/UiBinderSuite.java
similarity index 77%
rename from user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
rename to user/test/com/google/gwt/uibinder/UiBinderSuite.java
index e251075..a5d4678 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderSuite.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -18,27 +18,29 @@
 import com.google.gwt.junit.tools.GWTTestSuite;
 import com.google.gwt.uibinder.client.UiBinderUtilTest;
 import com.google.gwt.uibinder.test.client.InnerWidgetTest;
-import com.google.gwt.uibinder.test.client.TestParameterizedWidgets;
+import com.google.gwt.uibinder.test.client.ParameterizedWidgetsTest;
 import com.google.gwt.uibinder.test.client.UiBinderTest;
+import com.google.gwt.uibinder.test.client.UiChildTest;
 
 import junit.framework.Test;
 
 /**
  * Test suite for UiBinder GWTTestCases.
  */
-public class UiBinderGwtSuite {
+public class UiBinderSuite {
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite(
-        "Test suite for UiBinder GWTTestCases");
+        "Integration tests for UiBinder");
 
+    suite.addTestSuite(InnerWidgetTest.class);
     suite.addTestSuite(UiBinderTest.class);
     suite.addTestSuite(UiBinderUtilTest.class);
-    suite.addTestSuite(TestParameterizedWidgets.class);
-    suite.addTestSuite(InnerWidgetTest.class);
+    suite.addTestSuite(UiChildTest.class);
+    suite.addTestSuite(ParameterizedWidgetsTest.class);
 
     return suite;
   }
 
-  private UiBinderGwtSuite() {
+  private UiBinderSuite() {
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
index 4eded61..70381bd 100644
--- a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
@@ -32,7 +32,7 @@
 
   @Override
   public String getModuleName() {
-    return "com.google.gwt.uibinder.UiBinder";
+    return "com.google.gwt.uibinder.test.UiBinderSuite";
   }
 
   public void testAttachToDomAndGetChildUnattached() {
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/AbsolutePanelParserTest.java b/user/test/com/google/gwt/uibinder/elementparsers/AbsolutePanelParserTest.java
index 2e981f7..8cdbabd 100644
--- a/user/test/com/google/gwt/uibinder/elementparsers/AbsolutePanelParserTest.java
+++ b/user/test/com/google/gwt/uibinder/elementparsers/AbsolutePanelParserTest.java
@@ -95,7 +95,7 @@
     b.append("  <g:at left='1' top='2'>");
     b.append("    <g:Button/>");
     b.append("  </g:at>");
-    b.append("  <g:TextBox/>");
+    b.append("  <g:Label/>");
     b.append("  <g:at left='10' top='20'>");
     b.append("    <g:Label/>");
     b.append("  </g:at>");
@@ -104,7 +104,7 @@
     tester.parse(b.toString());
 
     assertStatements("fieldName.add(<g:Button>, 1, 2);",
-        "fieldName.add(<g:TextBox>);", "fieldName.add(<g:Label>, 10, 20);");
+        "fieldName.add(<g:Label>);", "fieldName.add(<g:Label>, 10, 20);");
   }
 
   private void assertStatements(String... expected) {
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java b/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java
index 9842ee5..574abf6 100644
--- a/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java
+++ b/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java
@@ -32,43 +32,13 @@
   private static final String PARSED_TYPE = "com.google.gwt.user.client.ui.HasUiChildren";
   private static final String UIBINDER = "<g:HasUiChildren>" + CHILDREN
       + "</g:HasUiChildren>";
-  private ElementParserTester tester;
-
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    MockJavaResource itemSubclass = new MockJavaResource(PARSED_TYPE) {
-      @Override
-      public CharSequence getContent() {
-        StringBuffer code = new StringBuffer();
-        code.append("package com.google.gwt.user.client.ui;\n");
-        code.append("import com.google.gwt.uibinder.client.UiChild;\n");
-        code.append("public class HasUiChildren {\n");
-        code.append("  public HasUiChildren() {\n");
-        code.append("  }\n\n");
-        code.append("  @UiChild\n");
-        code.append("  void addChild(Object child) {\n");
-        code.append("  }\n\n");
-        code.append("  @UiChild(tagname=\"achild\")\n");
-        code.append("  void addNamedChild(Object child) {\n");
-        code.append("  }\n\n");
-        code.append("  @UiChild(tagname=\"alimitedchild\", limit=1)\n");
-        code.append("  void addLimitedChild(Object child, int param1) {\n");
-        code.append("  }\n\n");
-        code.append("}\n");
-        return code;
-      }
-    };
-    UiBinderContext uiBinderCtx = new UiBinderContext();
-    tester = new ElementParserTester(PARSED_TYPE,
-        new UiChildParser(uiBinderCtx), itemSubclass);
-  }
 
   public void testAddChildWithParameters() throws SAXParseException,
       UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "<g:alimitedchild param1=\"1\"> <g:Label/> </g:alimitedchild>");
-
+    ElementParserTester tester = getTester();
+    
     FieldWriter w = tester.parse(b.toString());
     assertEquals(1, tester.writer.statements.size());
     assertEquals("Only call was: " + tester.writer.statements.get(0),
@@ -81,6 +51,8 @@
       UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "<g:alimitedchild> <g:Label/> </g:alimitedchild>");
+    ElementParserTester tester = getTester();
+
     FieldWriter w = tester.parse(b);
     assertEquals(1, tester.writer.statements.size());
     assertEquals("Only call was: " + tester.writer.statements.get(0),
@@ -89,16 +61,18 @@
     assertNull("Parser should never set an initializer.", w.getInitializer());
   }
 
-  public void testAddLimitedChildInvalid() throws SAXParseException {
+  public void testAddLimitedChildInvalid() throws SAXParseException, UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "  <g:alimitedchild param1=\"1\"> <g:Label/> </g:alimitedchild>"
             + "  <g:alimitedchild param1=\"2\"> <g:Label/> </g:alimitedchild>");
+    ElementParserTester tester = getTester();
+
     try {
       tester.parse(b.toString());
       fail();
     } catch (UnableToCompleteException exception) {
       assertEquals(1, tester.writer.statements.size());
-      assertEquals("Only call was: " + tester.writer.statements.get(0),
+      assertEquals("Only call should be: " + tester.writer.statements.get(0),
           "fieldName.addLimitedChild(<g:Label>, 1);",
           tester.writer.statements.get(0));
     }
@@ -108,6 +82,7 @@
       UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "<g:achild > <g:Label/> </g:achild>");
+    ElementParserTester tester = getTester();
 
     FieldWriter w = tester.parse(b.toString());
     assertEquals(1, tester.writer.statements.size());
@@ -120,6 +95,7 @@
       UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "<g:badtag> <g:Label/> </g:badtag>");
+    ElementParserTester tester = getTester();
 
     FieldWriter w = tester.parse(b);
     assertEquals("No children should have been consumed.", 0,
@@ -134,8 +110,33 @@
     assertNull("Parser should never set an initializer.", w.getInitializer());
   }
 
-  public void testIncompatibleChild() throws SAXParseException {
+  public void testBadParamType() throws SAXParseException, UnableToCompleteException {
+    String b =
+      UIBINDER.replaceAll(CHILDREN, "<g:aIntChild > <g:Widget/> </g:aIntChild>");
+    ElementParserTester tester = getBadParamTester();
+
+    try {
+      tester.parse(b.toString());
+      fail("Expected to choke on non-object param type.");
+    } catch (UnableToCompleteException exception) {
+      assertEquals(0, tester.writer.statements.size());
+    }
+  }
+
+  public void testComposedParamTypeChild() throws SAXParseException, UnableToCompleteException {
+    String b =
+        UIBINDER.replaceAll(CHILDREN, "<g:aComposedParamTypeChild > <g:CheckBox/> </g:aComposedParamTypeChild>");
+    ElementParserTester tester = getTester();
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("fieldName.addComposedParamTypeChild(<g:CheckBox>);", tester.writer.statements.get(0));
+    assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testIncompatibleChild() throws SAXParseException, UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN, "<g:child> <div/> </g:child>");
+    ElementParserTester tester = getTester();
 
     try {
       tester.parse(b);
@@ -145,9 +146,21 @@
     }
   }
 
-  public void testMultipleChildrenInvalid() throws SAXParseException {
+  public void testInterfaceChild() throws SAXParseException, UnableToCompleteException {
+    String b =
+        UIBINDER.replaceAll(CHILDREN, "<g:anInterfaceChild > <g:IsWidget/> </g:anInterfaceChild>");
+    ElementParserTester tester = getTester();
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("fieldName.addInterfaceChild(<g:IsWidget>);", tester.writer.statements.get(0));
+    assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testMultipleChildrenInvalid() throws SAXParseException, UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN,
         "<g:child ><g:Label/><g:Label/></g:child>");
+    ElementParserTester tester = getTester();
 
     try {
       tester.parse(b.toString());
@@ -157,9 +170,33 @@
     }
   }
 
+  public void testNonWidgetChild() throws SAXParseException, UnableToCompleteException {
+    String b =
+        UIBINDER.replaceAll(CHILDREN, "<g:aSpecificChild > <g:MenuItem/> </g:aSpecificChild>");
+    ElementParserTester tester = getTester();
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("fieldName.addSpecificTypeOfChild(<g:MenuItem>);", tester.writer.statements.get(0));
+    assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testNoAttributes() throws SAXParseException, UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN, "<g:achild ui:field='oops'> <g:Label/> </g:achild>");
+    ElementParserTester tester = getTester();
+
+    try {
+      tester.parse(b.toString());
+      fail("Expected to choke on disallowed parameter.");
+    } catch (UnableToCompleteException exception) {
+      assertEquals(0, tester.writer.statements.size());
+    }
+  }
+  
   public void testNoParameterChild() throws SAXParseException,
       UnableToCompleteException {
     String b = UIBINDER.replaceAll(CHILDREN, "<g:child> <g:Label/> </g:child>");
+    ElementParserTester tester = getTester();
 
     FieldWriter w = tester.parse(b);
     assertEquals(1, tester.writer.statements.size());
@@ -167,4 +204,128 @@
     assertEquals("fieldName.addChild(<g:Label>);",
         tester.writer.statements.get(0));
   }
+  
+  public void testParamTypeChild() throws SAXParseException, UnableToCompleteException {
+    String b =
+        UIBINDER.replaceAll(CHILDREN, "<g:aParamTypeChild > <g:ParamTypeImpl/> </g:aParamTypeChild>");
+    ElementParserTester tester = getTester();
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("fieldName.addParamTypeChild(<g:ParamTypeImpl>);", tester.writer.statements.get(0));
+    assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testTypeMismatchChild() throws SAXParseException, UnableToCompleteException {
+    String b =
+      UIBINDER.replaceAll(CHILDREN, "<g:aSpecificChild > <g:Widget/> </g:aSpecificChild>");
+    ElementParserTester tester = getTester();
+
+    try {
+      tester.parse(b.toString());
+      fail("Expected to choke on type mismatch.");
+    } catch (UnableToCompleteException exception) {
+      assertEquals(0, tester.writer.statements.size());
+    }
+  }
+  
+  private ElementParserTester getBadParamTester() throws UnableToCompleteException {
+    MockJavaResource invalidParamType = new MockJavaResource(PARSED_TYPE) {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("import com.google.gwt.uibinder.client.UiChild;\n");
+        code.append("public class HasUiChildren {\n");
+        code.append("  @UiChild\n");
+        code.append("  void addIntChild(int child) {\n");
+        code.append("  }\n\n");
+        code.append("}\n");
+        return code;
+      }
+    };
+    return new ElementParserTester(PARSED_TYPE,
+        new UiChildParser(new UiBinderContext()), invalidParamType);
+  }
+
+  private ElementParserTester getTester() throws UnableToCompleteException {
+    MockJavaResource editor = new MockJavaResource("com.google.gwt.user.client.ui.Editor") {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("public interface Editor<T>{\n");
+        code.append("}\n");
+        return code;
+      }
+    };
+    MockJavaResource checkBox = new MockJavaResource("com.google.gwt.user.client.ui.CheckBox") {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("public class CheckBox extends Widget implements Editor<Boolean> {");
+        code.append("}\n");
+        return code;
+      }
+    };
+    MockJavaResource paramType = new MockJavaResource("com.google.gwt.user.client.ui.ParamType") {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("public interface ParamType<T> {}\n");
+        return code;
+      }
+    };
+
+    MockJavaResource paramTypeImpl =
+        new MockJavaResource("com.google.gwt.user.client.ui.ParamTypeImpl") {
+          @Override
+          public CharSequence getContent() {
+            StringBuffer code = new StringBuffer();
+            code.append("package com.google.gwt.user.client.ui;\n");
+            code.append("public class ParamTypeImpl<T> implements ParamType<T> {}\n");
+            return code;
+          }
+        };
+
+    MockJavaResource hasUiChildren = new MockJavaResource(PARSED_TYPE) {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("import com.google.gwt.uibinder.client.UiChild;\n");
+        code.append("import com.google.gwt.event.dom.client.ClickHandler;\n");
+        code.append("public class HasUiChildren<T> {\n");
+
+        code.append("  @UiChild\n");
+        code.append("  void addChild(Object child) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"achild\")\n");
+        code.append("  void addNamedChild(Object child) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"alimitedchild\", limit=1)\n");
+        code.append("  void addLimitedChild(Object child, int param1) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"aSpecificChild\")\n");
+        code.append("  void addSpecificTypeOfChild(MenuItem child) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"anInterfaceChild\")\n");
+        code.append("  void addInterfaceChild(IsWidget child) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"aParamTypeChild\")\n");
+        code.append("  void addParamTypeChild(ParamType<T> child) {}\n\n");
+
+        code.append("  @UiChild(tagname=\"aComposedParamTypeChild\")\n");
+        code.append("  <W extends IsWidget & Editor<T>> void \n");
+        code.append("  addComposedParamTypeChild(W child) {}\n\n");
+        code.append("}\n");
+        return code;
+      }
+    };
+
+    return new ElementParserTester(PARSED_TYPE, new UiChildParser(new UiBinderContext()),
+        paramType, paramTypeImpl, editor, checkBox, hasUiChildren);
+  }
 }
diff --git a/user/test/com/google/gwt/uibinder/test/LazyWidgetBuilderSuite.gwt.xml b/user/test/com/google/gwt/uibinder/test/LazyWidgetBuilderSuite.gwt.xml
new file mode 100644
index 0000000..08d6ce9
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/LazyWidgetBuilderSuite.gwt.xml
@@ -0,0 +1,19 @@
+<!-- Copyright 2011 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Tests that rely on the useLazyWidgetBuilders switch                    -->
+<module>
+  <inherits name="com.google.gwt.user.User" />
+  <inherits name="com.google.gwt.debug.Debug"/>
+  <set-configuration-property name="UiBinder.useLazyWidgetBuilders" value="true"/>
+</module>
diff --git a/user/test/com/google/gwt/uibinder/test/UiBinderSuite.gwt.xml b/user/test/com/google/gwt/uibinder/test/UiBinderSuite.gwt.xml
new file mode 100644
index 0000000..f658571
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/UiBinderSuite.gwt.xml
@@ -0,0 +1,18 @@
+<!-- Copyright 2008 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- UiBinder integration tests                                             -->
+<module>
+  <inherits name="com.google.gwt.user.User" />
+  <inherits name="com.google.gwt.debug.Debug"/>
+</module>
diff --git a/user/test/com/google/gwt/uibinder/test/UiBinderTestApp.gwt.xml b/user/test/com/google/gwt/uibinder/test/UiBinderTestApp.gwt.xml
index 75532fb..bf3483a 100644
--- a/user/test/com/google/gwt/uibinder/test/UiBinderTestApp.gwt.xml
+++ b/user/test/com/google/gwt/uibinder/test/UiBinderTestApp.gwt.xml
@@ -11,7 +11,7 @@
 <!-- implied. License for the specific language governing permissions and   -->
 <!-- limitations under the License.                                         -->
 
-<!-- GWT UiBinder support.                                                  -->
+<!-- Allows the test app to be run directly.                                -->
 <module>
   <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.debug.Debug"/>
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
index 9ec9d8c..761b457 100644
--- a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -648,7 +648,8 @@
     public CharSequence getContent() {
       StringBuffer code = new StringBuffer();
       code.append("package com.google.gwt.user.client.ui;\n");
-      code.append("public class Widget extends UIObject {\n");
+      code.append("public class Widget extends UIObject implements IsWidget {\n");
+      code.append("  public Widget asWidget() { return this; }");
       code.append("}\n");
       return code;
     }
diff --git a/user/test/com/google/gwt/uibinder/test/client/Abstract.java b/user/test/com/google/gwt/uibinder/test/client/Abstract.java
index 7884a17..dd83cd2 100644
--- a/user/test/com/google/gwt/uibinder/test/client/Abstract.java
+++ b/user/test/com/google/gwt/uibinder/test/client/Abstract.java
@@ -20,7 +20,7 @@
 
 /**
  * Used by {@link ParameterizedWidget}. 
- * @see TestParameterizedWidgets
+ * @see ParameterizedWidgetsTest
  */
 abstract class Abstract<T> extends Widget {
   Abstract() {
diff --git a/user/test/com/google/gwt/uibinder/test/client/InnerWidgetTest.java b/user/test/com/google/gwt/uibinder/test/client/InnerWidgetTest.java
index bc04e5c..95f161b 100644
--- a/user/test/com/google/gwt/uibinder/test/client/InnerWidgetTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/InnerWidgetTest.java
@@ -41,7 +41,7 @@
 
   @Override
   public String getModuleName() {
-    return "com.google.gwt.uibinder.test.UiBinderTestApp";
+    return "com.google.gwt.uibinder.test.UiBinderSuite";
   }
 
   public void testHappy() {
diff --git a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
index 1a6213c..a950ba2 100644
--- a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
+++ b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
@@ -23,7 +23,7 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Used by {@link TestParameterizedWidgets}.
+ * Used by {@link ParameterizedWidgetsTest}.
  */
 class ParameterizedWidget<T> extends Composite {
   interface Binder extends UiBinder<Widget, ParameterizedWidget<?>> {
diff --git a/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidgetsTest.java
similarity index 88%
rename from user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java
rename to user/test/com/google/gwt/uibinder/test/client/ParameterizedWidgetsTest.java
index 398c95a..3cb27bb 100644
--- a/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java
+++ b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidgetsTest.java
@@ -20,10 +20,10 @@
 /**
  * Test that {@literal @}UiFactory and parameterized return types get along.
  */
-public class TestParameterizedWidgets extends GWTTestCase {
+public class ParameterizedWidgetsTest extends GWTTestCase {
   @Override
   public String getModuleName() {
-    return "com.google.gwt.uibinder.test.UiBinderTestApp";
+    return "com.google.gwt.uibinder.test.UiBinderSuite";
   }
 
   public void testHappy() {
diff --git a/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.Ui.ui.xml b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.Ui.ui.xml
new file mode 100644
index 0000000..467f090
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.Ui.ui.xml
@@ -0,0 +1,21 @@
+<!--                                                                        -->
+<!-- Copyright 2011 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:demo='urn:import:com.google.gwt.uibinder.test.client'
+  >
+  <div>
+    <div ui:field='div'><demo:SafeHtmlObject ui:field='safeObject' name="Bob" /></div>
+  </div>
+</ui:UiBinder>
diff --git a/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.java b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.java
new file mode 100644
index 0000000..0883c14
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlAsComponentsTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.test.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+
+/**
+ * Test the use of SafeHtml objects as UiBinder components. E.g.
+ * 
+ * <pre>
+ * &lt;div>&lt;my:SafeHtmlThingy foo="bar"/>&lt;/div></pre>
+ */
+public class SafeHtmlAsComponentsTest extends GWTTestCase {
+  static class Ui {
+    interface Binder extends UiBinder<Element, Ui> {
+    }
+
+    static final Binder binder = GWT.create(Binder.class);
+
+    @UiField
+    DivElement div;
+    @UiField
+    SafeHtmlObject safeObject;
+
+    Ui() {
+      binder.createAndBindUi(this);
+    }
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.uibinder.test.LazyWidgetBuilderSuite";
+  }
+
+  public void testSafeHtml() {
+    Ui domUi = new Ui();
+    assertNotNull(domUi.safeObject);
+    assertEquals(domUi.safeObject.asString(), domUi.div.getInnerHTML());
+    assertEquals("Hello <b>Bob</b>".toLowerCase(), domUi.div.getInnerHTML().toLowerCase());
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/test/client/SafeHtmlObject.java b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlObject.java
new file mode 100644
index 0000000..4f9b0d2
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/SafeHtmlObject.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.test.client;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * Used by UiBinderTest.
+ */
+public class SafeHtmlObject implements SafeHtml {
+  private String name = "unset";
+
+  @Override
+  public String asString() {
+    return "Hello <b>" + name + "</b>";
+  }
+
+  public void setName(String name) {
+    // Bad - should escape this name
+    this.name = name;
+  }
+}
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 396b92f..91a5203 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
@@ -53,7 +53,7 @@
 
   @Override
   public String getModuleName() {
-    return "com.google.gwt.uibinder.test.UiBinderTestApp";
+    return "com.google.gwt.uibinder.test.UiBinderSuite";
   }
 
   @Override
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiChildTest.Ui.ui.xml b/user/test/com/google/gwt/uibinder/test/client/UiChildTest.Ui.ui.xml
new file mode 100644
index 0000000..2a7f972
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/UiChildTest.Ui.ui.xml
@@ -0,0 +1,34 @@
+<!--                                                                        -->
+<!-- Copyright 2011 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:test='urn:import:com.google.gwt.uibinder.test.client'
+  >
+  <g:HTMLPanel>
+    <test:UiChildTest.MyPanel ui:field="panel">
+      <test:childAsInterface>
+        <test:UiChildTest.MyView ui:field="asInterface" name="interface"/>
+      </test:childAsInterface>
+      <test:childAsImpl>
+        <test:UiChildTest.MyViewImpl ui:field="asImpl" name="impl"/>
+      </test:childAsImpl>
+      <test:uiobject>
+        <g:MenuItem ui:field="menuItem">"Menuliscious"</g:MenuItem>
+      </test:uiobject>
+      <test:arbitrarytype>
+        <test:UiChildTest.ArbitraryType  ui:field="arbitrary"/>
+      </test:arbitrarytype>
+    </test:UiChildTest.MyPanel>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiChildTest.java b/user/test/com/google/gwt/uibinder/test/client/UiChildTest.java
new file mode 100644
index 0000000..e2bc846
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/UiChildTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2011 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.test.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiChild;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Integration tests of @UiChild.
+ */
+public class UiChildTest extends GWTTestCase {
+  static class ArbitraryType {
+  }
+
+  static class MyPanel extends FlowPanel {
+    MenuItem menuItem;
+    private ArbitraryType arbitrary;
+
+    @UiChild(tagname = "childAsImpl")
+    public void addChildAsImpl(MyViewImpl child) {
+      this.add(child);
+    }
+
+    @UiChild(tagname = "childAsInterface")
+    public void addChildAsInterface(MyView child) {
+      this.add(child);
+    }
+
+    @UiChild
+    public void addUiObject(MenuItem item) {
+      this.menuItem = item;
+    }
+
+    @UiChild
+    void addArbitraryType(ArbitraryType thingy) {
+      this.arbitrary = thingy;
+    }
+  }
+
+  interface MyView extends IsWidget {
+    String getName();
+
+    void setName(String name);
+  }
+
+  static class MyViewImpl extends Composite implements MyView {
+    MyViewImpl() {
+      initWidget(new Label());
+    }
+
+    public String getName() {
+      return asLabel().getText();
+    }
+
+    public void setName(String name) {
+      asLabel().setText(name);
+    }
+
+    private Label asLabel() {
+      return ((Label) getWidget());
+    }
+  }
+
+  static class Ui extends Composite {
+    interface Binder extends UiBinder<HTMLPanel, Ui> {
+    }
+
+    static final Binder BINDER = GWT.create(Binder.class);
+
+    @UiField
+    MyViewImpl asImpl;
+
+    @UiField
+    MyView asInterface;
+
+    @UiField
+    MyPanel panel;
+    
+    @UiField
+    MenuItem menuItem;
+    
+    @UiField
+    ArbitraryType arbitrary;
+
+    Ui() {
+      initWidget(BINDER.createAndBindUi(this));
+    }
+
+    @Override
+    public Widget asWidget() {
+      return this;
+    }
+
+    @UiFactory
+    MyView createForInterface() {
+      return new MyViewImpl();
+    }
+  }
+
+  Ui subject;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.uibinder.test.UiBinderSuite";
+  }
+
+  public void testIsWidget() {
+    assertNotNull(subject.asInterface);
+    assertEquals("interface", subject.asInterface.getName());
+    assertSame(subject.panel, subject.asInterface.asWidget().getParent());
+  }
+
+  public void testSimple() {
+    assertNotNull(subject.asImpl);
+    assertEquals("impl", subject.asImpl.getName());
+    assertSame(subject.panel, subject.asImpl.getParent());
+  }
+  
+  public void testUiObject() {
+    assertSame(subject.menuItem, subject.panel.menuItem);
+  }
+  
+  public void testPojo() {
+    assertSame(subject.arbitrary, subject.panel.arbitrary);
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    subject = new Ui();
+  }
+}