Redesign of CaptionPanel, lots of additional CaptionPanel unit tests, and a few related changes in SimplePanel (updated to use new Element class) and Node (javadoc tweak).

Patch by: bruce, jlabanca
Review by: jlabanca (pair programming)
Issue: 2307

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2468 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/dom/client/Node.java b/user/src/com/google/gwt/dom/client/Node.java
index b0fd27e..f501ef9 100644
--- a/user/src/com/google/gwt/dom/client/Node.java
+++ b/user/src/com/google/gwt/dom/client/Node.java
@@ -162,11 +162,11 @@
 
   /**
    * Inserts the node newChild before the existing child node refChild. If
-   * refChild is null, insert newChild at the end of the list of children.
+   * refChild is <code>null</code>, insert newChild at the end of the list of children.
    * 
    * @param newChild The node to insert
-   * @param refChild The reference node, i.e., the node before which the new
-   *          node must be inserted
+   * @param refChild The reference node (that is, the node before which the new
+   *          node must be inserted), or <code>null</code> 
    * @return The node being inserted
    */
   public final native Node insertBefore(Node newChild, Node refChild) /*-{
diff --git a/user/src/com/google/gwt/user/TitledPanel.gwt.xml b/user/src/com/google/gwt/user/CaptionPanel.gwt.xml
similarity index 83%
rename from user/src/com/google/gwt/user/TitledPanel.gwt.xml
rename to user/src/com/google/gwt/user/CaptionPanel.gwt.xml
index 785daf4..8a2dda6 100644
--- a/user/src/com/google/gwt/user/TitledPanel.gwt.xml
+++ b/user/src/com/google/gwt/user/CaptionPanel.gwt.xml
@@ -21,8 +21,8 @@
   <inherits name="com.google.gwt.user.UserAgent"/>
 
   <!-- Mozilla has a different implementation to handle rendering issues -->
-  <replace-with class="com.google.gwt.user.client.ui.TitledPanel.TitledPanelImplMozilla">
-    <when-type-is class="com.google.gwt.user.client.ui.TitledPanel.TitledPanelImpl"/>
+  <replace-with class="com.google.gwt.user.client.ui.CaptionPanel.CaptionPanelImplMozilla">
+    <when-type-is class="com.google.gwt.user.client.ui.CaptionPanel.CaptionPanelImpl"/>
     <any>
       <when-property-is name="user.agent" value="gecko"/>
       <when-property-is name="user.agent" value="gecko1_8"/>
@@ -30,8 +30,8 @@
   </replace-with>
 
   <!-- Safari has a different implementation to handle rendering issues -->
-  <replace-with class="com.google.gwt.user.client.ui.TitledPanel.TitledPanelImplSafari">
-    <when-type-is class="com.google.gwt.user.client.ui.TitledPanel.TitledPanelImpl"/>
+  <replace-with class="com.google.gwt.user.client.ui.CaptionPanel.CaptionPanelImplSafari">
+    <when-type-is class="com.google.gwt.user.client.ui.CaptionPanel.CaptionPanelImpl"/>
     <when-property-is name="user.agent" value="safari"/>
   </replace-with>
 </module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 4bac2f6..af4e794 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -37,7 +37,7 @@
    <inherits name="com.google.gwt.user.SplitPanel"/>
    <inherits name="com.google.gwt.user.StackPanel"/>
    <inherits name="com.google.gwt.user.ListBox" />
-   <inherits name="com.google.gwt.user.TitledPanel" />
+   <inherits name="com.google.gwt.user.CaptionPanel" />
    <inherits name="com.google.gwt.user.Window" />
    <inherits name="com.google.gwt.user.Accessibility"/>
 </module>
diff --git a/user/src/com/google/gwt/user/client/ui/CaptionPanel.java b/user/src/com/google/gwt/user/client/ui/CaptionPanel.java
index 968b380..b064a73 100644
--- a/user/src/com/google/gwt/user/client/ui/CaptionPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/CaptionPanel.java
@@ -16,27 +16,44 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.FieldSetElement;
+import com.google.gwt.dom.client.LegendElement;
 import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.Element;
+
+import java.util.Iterator;
 
 /**
- * A panel that wraps its contents in a border with a caption that appears in the
- * upper left corner of the border. This is an implementation of the fieldset
- * HTML element.
+ * A panel that wraps its contents in a border with a caption that appears in
+ * the upper left corner of the border. This is an implementation of the
+ * fieldset HTML element.
  */
-public class CaptionPanel extends SimplePanel {
+public class CaptionPanel extends Composite implements HasWidgets {
   /**
    * Implementation class without browser-specific hacks.
    */
   public static class CaptionPanelImpl {
-    public void setCaption(Element fieldset, Element legend, String caption) {
-      if (caption != null && !"".equals(caption)) {
-        DOM.setInnerHTML(legend, caption);
-        DOM.insertChild(fieldset, legend, 0);
-      } else if (DOM.getParent(legend) != null) {
-        DOM.removeChild(fieldset, legend);
+    public void setCaption(FieldSetElement fieldset, Element legend, String caption, boolean asHTML) {
+      // TODO(bruce): rewrite to be inlinable
+      assert (caption != null);
+
+      if (asHTML) {
+        legend.setInnerHTML(caption);
+      } else {
+        legend.setInnerText(caption);
+      }
+
+      if (!"".equals(caption)) {
+        // This is formulated to become an append (if there's no widget), an
+        // insertion at index 0 (if there is a widget but no legend already), or
+        // a no-op (if the legend is already in place).
+        fieldset.insertBefore(legend, fieldset.getFirstChild());
+      } else if (legend.getParentNode() != null) {
+        // We remove the legend from the DOM because leaving it in with an empty
+        // string renders as an ugly gap in the top border on some browsers.
+        fieldset.removeChild(legend);
       }
     }
   }
@@ -46,24 +63,26 @@
    */
   public static class CaptionPanelImplMozilla extends CaptionPanelImpl {
     @Override
-    public void setCaption(final Element fieldset, Element legend, String caption) {
-      DOM.setStyleAttribute(fieldset, "display", "none");
-      super.setCaption(fieldset, legend, caption);
-      DOM.setStyleAttribute(fieldset, "display", "");
+    public void setCaption(final FieldSetElement fieldset, Element legend, String caption,
+        boolean asHTML) {
+      fieldset.getStyle().setProperty("display", "none");
+      super.setCaption(fieldset, legend, caption, asHTML);
+      fieldset.getStyle().setProperty("display", "");
     }
   }
-  
+
   /**
    * Implementation class that handles Safari rendering issues.
    */
   public static class CaptionPanelImplSafari extends CaptionPanelImpl {
     @Override
-    public void setCaption(final Element fieldset, Element legend, String caption) {
-      DOM.setStyleAttribute(fieldset, "visibility", "hidden");
-      super.setCaption(fieldset, legend, caption);
+    public void setCaption(final FieldSetElement fieldset, Element legend, String caption,
+        boolean asHTML) {
+      fieldset.getStyle().setProperty("visibility", "hidden");
+      super.setCaption(fieldset, legend, caption, asHTML);
       DeferredCommand.addCommand(new Command() {
         public void execute() {
-          DOM.setStyleAttribute(fieldset, "visibility", "");
+          fieldset.getStyle().setProperty("visibility", "");
         }
       });
     }
@@ -77,51 +96,133 @@
   /**
    * The legend element used as the caption.
    */
-  private Element legend;
+  private LegendElement legend;
 
   /**
-   * The caption at the top of the border.
+   * Constructs a CaptionPanel with an empty caption.
    */
-  private String caption;
+  public CaptionPanel() {
+    this("", false);
+  }
+
+  /**
+   * Constructs a CaptionPanel with specified caption text.
+   * 
+   * @param caption the text of the caption, which is automatically escaped
+   */
+  public CaptionPanel(String captionText) {
+    this(captionText, false);
+  }
 
   /**
    * Constructs a CaptionPanel having the specified caption.
    * 
    * @param caption the caption to display
+   * @param asHTML if <code>true</code>, the <code>caption</code> param is
+   *            interpreted as HTML; otherwise, <code>caption</code> is
+   *            treated as text and automatically escaped
    */
-  public CaptionPanel(String caption) {
-    super(DOM.createElement("fieldset"));
-    legend = DOM.createElement("legend");
-    DOM.appendChild(getElement(), legend);
-    setCaption(caption);
+  public CaptionPanel(String caption, boolean asHTML) {
+    FieldSetElement fieldSet = Document.get().createFieldSetElement();
+    initWidget(new SimplePanel(fieldSet));
+    legend = Document.get().createLegendElement();
+    fieldSet.appendChild(legend);
+    if (asHTML) {
+      setCaptionHTML(caption);
+    } else {
+      setCaptionText(caption);
+    }
+  }
+
+  public void add(Widget w) {
+    ((SimplePanel) getWidget()).add(w);
   }
 
   /**
-   * Constructor.
+   * @return the caption as HTML; note that if the caption was previously set
+   *         using {@link #setCaptionText(String)}, the return value is
+   *         undefined
+   */
+  public String getCaptionHTML() {
+    String html = legend.getInnerHTML();
+    assert (html != null);
+    return html;
+  }
+
+  /**
+   * @return the caption as text; note that if the caption was previously set
+   *         using {@link #setCaptionHTML(String)}, the return value is
+   *         undefined
+   */
+  public String getCaptionText() {
+    String text = legend.getInnerText();
+    assert (text != null);
+    return text;
+  }
+
+  /**
+   * Accesses the content widget, if present.
    * 
-   * @param caption the caption to display
-   * @param w the widget to add to the panel
+   * @return the content widget specified previously in
+   *         {@link #setContentWidget(Widget)}
    */
-  public CaptionPanel(String caption, Widget w) {
-    this(caption);
-    setWidget(w);
+  public Widget getContentWidget() {
+    return ((SimplePanel) getWidget()).getWidget();
   }
 
   /**
-   * @return the caption on top of the border
+   * Iterates over the singular content widget, if present.
    */
-  public String getCaption() {
-    return this.caption;
+  public Iterator<Widget> iterator() {
+    return ((SimplePanel) getWidget()).iterator();
   }
 
   /**
-   * Set the caption in the border. Pass in null or an empty string to remove the
-   * caption completely, leaving just a box.
+   * Removes the specified widget, although in practice the specified widget
+   * must be the content widget.
    * 
-   * @param caption the new caption
+   * @param w the widget to remove; note that anything other than the Widget
+   *            returned by {@link #getContentWidget()} will have no effect
    */
-  public void setCaption(String caption) {
-    this.caption = caption;
-    impl.setCaption(getElement(), legend, caption);
+  public boolean remove(Widget w) {
+    return ((SimplePanel) getWidget()).remove(w);
+  }
+
+  /**
+   * Sets the caption for the panel. Pass in empty string to remove the caption
+   * completely, leaving just the unadorned panel.
+   * 
+   * @param html HTML for the new caption; must not be <code>null</code>
+   */
+  public void setCaptionHTML(String html) {
+    assert (html != null);
+    impl.setCaption(FieldSetElement.as(getElement()), legend, html, true);
+  }
+
+  /**
+   * Sets the caption for the panel. Pass in empty string to remove the caption
+   * completely, leaving just the unadorned panel.
+   * 
+   * @param html HTML for the new caption; must not be <code>null</code>
+   */
+  public void setCaptionText(String text) {
+    assert (text != null);
+    impl.setCaption(FieldSetElement.as(getElement()), legend, text, false);
+  }
+
+  /**
+   * Sets or replaces the content widget within the CaptionPanel.
+   * 
+   * @param w the content widget to be set
+   */
+  public void setContentWidget(Widget w) {
+    ((SimplePanel) getWidget()).setWidget(w);
+  }
+
+  /**
+   * Removes the content widget.
+   */
+  public void clear() {
+    ((SimplePanel) getWidget()).clear();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/SimplePanel.java b/user/src/com/google/gwt/user/client/ui/SimplePanel.java
index 9d66328..6ed6a1b 100644
--- a/user/src/com/google/gwt/user/client/ui/SimplePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SimplePanel.java
@@ -16,7 +16,7 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -54,8 +54,7 @@
   public void add(Widget w) {
     // Can't add() more than one widget to a SimplePanel.
     if (getWidget() != null) {
-      throw new IllegalStateException(
-          "SimplePanel can only contain one child widget");
+      throw new IllegalStateException("SimplePanel can only contain one child widget");
     }
     setWidget(w);
   }
@@ -107,7 +106,7 @@
     orphan(w);
 
     // Physical detach.
-    DOM.removeChild(getContainerElement(), w.getElement());
+    getContainerElement().removeChild(w.getElement());
 
     // Logical detach.
     widget = null;
@@ -151,9 +150,13 @@
    * be the container for the panel's child widget. This can be useful when you
    * want to create a simple panel that decorates its contents.
    * 
+   * Note that this method continues to return the
+   * {@link com.google.gwt.user.client.Element} class defined in the
+   * <code>User</code> module to maintain backwards compatibility.
+   * 
    * @return the element to be used as the panel's container
    */
-  protected Element getContainerElement() {
+  protected com.google.gwt.user.client.Element getContainerElement() {
     return getElement();
   }
 }
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 304fdcd..e5fb785 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -76,7 +76,7 @@
         "Test for suite for the com.google.gwt.ui module");
 
     suite.addTestSuite(AbsolutePanelTest.class);
-//    suite.addTestSuite(CaptionPanelTest.class);
+    suite.addTestSuite(CaptionPanelTest.class);
     suite.addTestSuite(CheckBoxTest.class);
     suite.addTestSuite(ClippedImagePrototypeTest.class);
     suite.addTestSuite(CommandExecutorTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java b/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java
index a6570b8..b11c4f0 100644
--- a/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.DOM;
 
 /**
  * Tests {@link CaptionPanel}.
@@ -27,48 +27,240 @@
   public String getModuleName() {
     return "com.google.gwt.user.User";
   }
-  
-  /**
-   * Test the basic setting and getting of the caption.
-   */
-  public void testSetCaption() {
-    // Null caption
-    CaptionPanel panel1 = new CaptionPanel(null);
-    assertNull(panel1.getCaption());
-    assertNull(DOM.getFirstChild(panel1.getElement()));
-    panel1.setCaption("");
-    assertEquals("", panel1.getCaption());
-    assertNull(DOM.getFirstChild(panel1.getElement()));
-    
-    // Actual caption
-    CaptionPanel panel2 = new CaptionPanel("my panel2");
-    assertEquals("my panel2", panel2.getCaption());
-    assertNotNull(DOM.getFirstChild(panel2.getElement()));
-    panel2.setCaption("my cool panel 2");
-    assertEquals("my cool panel 2", panel2.getCaption());
-    assertNotNull(DOM.getFirstChild(panel2.getElement()));
+
+  public void testHasWidgets() {
+    // With no caption.
+    HasWidgetsTester.testAll(new CaptionPanel());
+
+    // With a text caption.
+    HasWidgetsTester.testAll(new CaptionPanel("some text"));
+
+    // With a complex HTML caption.
+    HasWidgetsTester.testAll(new CaptionPanel("<legend>not the <i>actual</i> legend<legend>", true));
   }
-  
-  /**
-   * Test the setting and removal of a widget.
-   */
-  public void testSetWidget() {
-    // No Widget constructor
-    CaptionPanel panel1 = new CaptionPanel("no widget");
-    assertNull(panel1.getWidget());
-    
-    // Widget constructor
-    HTML widget2 = new HTML("widget 2");
-    CaptionPanel panel2 = new CaptionPanel("has widget", widget2);
-    assertEquals(widget2, panel2.getWidget());
-    
-    // Set widget
-    HTML widget3 = new HTML("widget 3");
-    CaptionPanel panel3 = new CaptionPanel("set widget", null);
-    assertNull(panel3.getWidget());
-    panel3.setWidget(widget3);
-    assertEquals(widget3, panel3.getWidget());
-    panel3.remove(widget3);
-    assertNull(panel3.getWidget());
+
+  public void testCaptionAcceptsEmptyStringAndRemovesLegendElement() {
+    // Ctor/HTML.
+    {
+      CaptionPanel panel = new CaptionPanel("");
+      assertEquals("", panel.getCaptionHTML());
+      assertNull(panel.getElement().getFirstChild());
+    }
+
+    // Setter/HTML.
+    {
+      CaptionPanel panel = new CaptionPanel("not null");
+      assertEquals("not null", panel.getCaptionHTML());
+
+      panel.setCaptionHTML("");
+      assertEquals("", panel.getCaptionHTML());
+      assertNull(panel.getElement().getFirstChild());
+    }
+
+    // Setter/Text.
+    {
+      CaptionPanel panel = new CaptionPanel("not null");
+      assertEquals("not null", panel.getCaptionHTML());
+
+      panel.setCaptionText("");
+      assertEquals("", panel.getCaptionHTML());
+      assertNull(panel.getElement().getFirstChild());
+    }
   }
+
+  /**
+   * When the caption is null, it needs to be actually removed from the DOM (to
+   * compensate for browser bugs). This formulation requires no widget to have
+   * been set first.
+   */
+  public void testCaptionAssertsAgainstNull() {
+    // Ctor.
+    {
+      try {
+        new CaptionPanel(null);
+        fail("Should've asserted!");
+      } catch (AssertionError e) {
+        // good to make it here
+      }
+    }
+
+    // Setter/HTML.
+    {
+      try {
+        CaptionPanel panel = new CaptionPanel("stuff");
+        panel.setCaptionHTML(null);
+        fail("Should've asserted!");
+      } catch (AssertionError e) {
+        // good to make it here
+      }
+    }
+
+    // Setter/Text.
+    {
+      try {
+        CaptionPanel panel = new CaptionPanel("stuff");
+        panel.setCaptionText(null);
+        fail("Should've asserted!");
+      } catch (AssertionError e) {
+        // good to make it here
+      }
+    }
+  }
+
+  public void testCtorAsHtmlFlag() {
+    String s = "this is <b>not</b> null";
+
+    // Ctor/Text.
+    {
+      CaptionPanel panel = new CaptionPanel(s, false);
+      assertEquals(s, panel.getCaptionText());
+      assertFalse(s.equals(panel.getCaptionHTML()));
+    }
+
+    // Ctor/HTML.
+    {
+      CaptionPanel panel = new CaptionPanel(s, true);
+      assertEquals("this is not null", panel.getCaptionText());
+      assertEquals(s, panel.getCaptionHTML());
+    }
+  }
+
+  public void testDefaultCaptionIsEmptyString() {
+    CaptionPanel panel = new CaptionPanel();
+    assertEquals("", panel.getCaptionText());
+    assertEquals("", panel.getCaptionHTML());
+    // Wigets may be supported in the future.
+    // assertNull(panel.getCaptionWidget());
+    assertNull(panel.getElement().getFirstChild());
+  }
+
+  public void testGetSetHTMLCaption() {
+    CaptionPanel panel = new CaptionPanel();
+    panel.setCaptionHTML("<b>bold</b>");
+    assertEquals("<b>bold</b>", panel.getCaptionHTML());
+    assertEquals("bold", panel.getCaptionText());
+  }
+
+  public void testGetSetTextCaption() {
+    String s = "this is <b>not</b> null";
+    CaptionPanel panel = new CaptionPanel();
+    panel.setCaptionText(s);
+    assertEquals(s, panel.getCaptionText());
+    assertFalse(s.equals(panel.getCaptionHTML()));
+  }
+
+  public void testOneArgCtorIsTextCaption() {
+    String s = "this is <b>not</b> null";
+    CaptionPanel panel = new CaptionPanel(s);
+    assertEquals(s, panel.getCaptionText());
+    assertFalse(s.equals(panel.getCaptionHTML()));
+  }
+
+  public void testGetSetContentWidget() {
+    {
+      // No Widget set in the default ctor.
+      CaptionPanel panel = new CaptionPanel("no widget");
+      assertNull(panel.getContentWidget());
+    }
+
+    {
+      // Set widget and remove() it.
+      CaptionPanel panel = new CaptionPanel("no widget yet");
+      assertNull(panel.getContentWidget());
+
+      HTML widget = new HTML("widget");
+      panel.setContentWidget(widget);
+      assertSame(widget, panel.getContentWidget());
+
+      panel.remove(widget);
+      assertNull(panel.getContentWidget());
+    }
+
+    {
+      // Set widget and clear() it.
+      CaptionPanel panel = new CaptionPanel("no widget yet");
+      assertNull(panel.getContentWidget());
+
+      HTML widget = new HTML("widget");
+      panel.setContentWidget(widget);
+      assertSame(widget, panel.getContentWidget());
+
+      panel.clear();
+      assertNull(panel.getContentWidget());
+    }
+
+    {
+      // Set widget and replace it.
+      CaptionPanel panel = new CaptionPanel("no widget yet");
+      assertNull(panel.getContentWidget());
+
+      HTML widget1 = new HTML("widget1");
+      panel.setContentWidget(widget1);
+      assertSame(widget1, panel.getContentWidget());
+
+      HTML widget2 = new HTML("widget2");
+      panel.setContentWidget(widget2);
+      assertSame(widget2, panel.getContentWidget());
+    }
+  }
+
+  public void testGetSetCaptionAmidstContentWidget() {
+    CaptionPanel panel = new CaptionPanel("caption");
+    HTML widget = new HTML("widget");
+    panel.setContentWidget(widget);
+    assertEquals("caption", panel.getCaptionText());
+    assertSame(widget, panel.getContentWidget());
+
+    {
+      // Set the caption to an empty string and verify that the legend element is removed
+      panel.setCaptionText("");
+      assertEquals("", panel.getCaptionText());
+      assertSame(widget, panel.getContentWidget());
+      Element panelFirstChild = panel.getElement().getFirstChildElement();
+      // The legend element ought to be removed from the DOM at this point.
+      assertFalse("legend".equalsIgnoreCase(panelFirstChild.getTagName()));
+      // (Perhaps redundantly) check that the one child is the content widget.
+      assertSame(panelFirstChild, widget.getElement());
+      assertNull(panelFirstChild.getNextSibling());
+    }
+
+    {
+      // Set the caption to a non-empty string and verify that the legend element is readded to the
+      // 0th index
+      panel.setCaptionText("new caption");
+      assertEquals("new caption", panel.getCaptionText());
+      assertSame(widget, panel.getContentWidget());
+      Element panelFirstChild = panel.getElement().getFirstChildElement();
+      // The legend element ought to be the 0th element in the DOM at this point.
+      assertTrue("legend".equalsIgnoreCase(panelFirstChild.getTagName()));
+      // Check that the second child is the content widget.
+      assertSame(panelFirstChild.getNextSibling(), widget.getElement());
+    }
+
+    {
+      // Set the caption to a non-empty string and verify that the legend element remains at the 0th
+      // index
+      panel.setCaptionText("newer caption");
+      assertEquals("newer caption", panel.getCaptionText());
+      assertSame(widget, panel.getContentWidget());
+      Element panelFirstChild = panel.getElement().getFirstChildElement();
+      // The legend element ought to be the 0th element in the DOM at this point.
+      assertTrue("legend".equalsIgnoreCase(panelFirstChild.getTagName()));
+      // Check that the second child is the content widget.
+      assertSame(panelFirstChild.getNextSibling(), widget.getElement());
+    }
+
+    {
+      // Remove the widget and verify the caption remains at the 0th index
+      panel.remove(widget);
+      assertEquals("newer caption", panel.getCaptionText());
+      assertNull(panel.getContentWidget());
+      Element panelFirstChild = panel.getElement().getFirstChildElement();
+      // The legend element ought to be the 0th element in the DOM at this point.
+      assertTrue("legend".equalsIgnoreCase(panelFirstChild.getTagName()));
+      // (Perhaps redundantly) check that the one child is the legend element.
+      assertNull(panelFirstChild.getNextSibling());
+    }
+  }
+
 }
diff --git a/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java b/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java
index efc4759..8771888 100644
--- a/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java
+++ b/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java
@@ -20,8 +20,8 @@
 import junit.framework.Assert;
 
 /**
- * All Widgets that implement HasWidgets should derive from this test case, and
- * make sure to run all of its test templates.
+ * All test cases for widgets that implement HasWidgets should derive from this
+ * test case, and make sure to run all of its test templates.
  */
 public abstract class HasWidgetsTester {
 
@@ -58,15 +58,13 @@
       // During onLoad, isAttached must be true, and the element be a descendant
       // of the body element.
       Assert.assertTrue(isAttached());
-      Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(),
-          getElement()));
+      Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(), getElement()));
     }
 
     protected void onUnload() {
       // During onUnload, everything must *still* be attached.
       Assert.assertTrue(isAttached());
-      Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(),
-          getElement()));
+      Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(), getElement()));
     }
   }
 
@@ -123,12 +121,13 @@
     // onUnload order.
     TestWidget widget = new TestWidget();
     adder.addChild(container, widget);
+    Assert.assertTrue(widget.isAttached());
+    Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(), widget.getElement()));
     container.remove(widget);
 
     // After removal, the widget should be detached.
     Assert.assertFalse(widget.isAttached());
-    Assert.assertFalse(DOM.isOrHasChild(RootPanel.getBodyElement(),
-        widget.getElement()));
+    Assert.assertFalse(DOM.isOrHasChild(RootPanel.getBodyElement(), widget.getElement()));
   }
 
   /**