blob: 4bbbb9c98be32c7d7184937b298b4fea97807017 [file] [log] [blame]
/*
* Copyright 2007 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.user.client.DOM;
import junit.framework.Assert;
import java.util.Iterator;
import java.util.Set;
/**
* 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 {
/**
* Used in test templates to allow the child class to specify how a widget
* will be added to its container. This is necessary because
* {@link HasWidgets#add(Widget)} is allowed to throw
* {@link UnsupportedOperationException}.
*/
interface WidgetAdder {
/**
* Adds the specified child to a container.
*/
void addChild(HasWidgets container, Widget child);
}
/**
* Default implementation used by containers for which
* {@link HasWidgets#add(Widget)} will not throw an exception.
*/
static class DefaultWidgetAdder implements WidgetAdder {
public void addChild(HasWidgets container, Widget child) {
container.add(child);
}
}
private static class TestWidget extends Widget {
TestWidget() {
setElement(DOM.createDiv());
}
@Override
protected void onLoad() {
// 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()));
}
@Override
protected void onUnload() {
// During onUnload, everything must *still* be attached.
Assert.assertTrue(isAttached());
Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(),
getElement()));
}
}
/**
* Runs all tests for {@link HasWidgets}. It is recommended that tests call
* this method or {@link #testAll(HasWidgets, WidgetAdder} so that future
* tests are automatically included.
*
* @param container
*/
static void testAll(HasWidgets container) {
testAll(container, new DefaultWidgetAdder());
}
/**
* Runs all tests for {@link HasWidgets}. It is recommended that tests call
* this method or {@link #testAll(HasWidgets, WidgetAdder)} so that future
* tests are automatically included.
*
* @param container
* @param adder
*/
static void testAll(HasWidgets container, WidgetAdder adder) {
testAll(container, adder, false);
}
/**
* Runs all tests for {@link HasWidgets}. It is recommended that tests call
* this method or {@link #testAll(HasWidgets, WidgetAdder)} so that future
* tests are automatically included.
*
* @param container the container widget to test
* @param adder the method of adding children
* @param supportsMultipleWidgets true if container supports multiple children
*/
static void testAll(HasWidgets container, WidgetAdder adder,
boolean supportsMultipleWidgets) {
testAttachDetachOrder(container, adder);
testRemovalOfNonExistantChild(container);
testDoAttachChildrenWithError(container, adder, supportsMultipleWidgets);
testDoAttachChildrenWithError(container, adder, supportsMultipleWidgets);
}
/**
* Tests attach and detach order, assuming that the container's
* {@link HasWidgets#add(Widget)} method does not throw
* {@link UnsupportedOperationException}.
*
* @param test
* @param container
* @see #testAttachDetachOrder(TestCase, HasWidgets,
* com.google.gwt.user.client.ui.HasWidgetsTester.WidgetAdder)
*/
static void testAttachDetachOrder(HasWidgets container) {
testAttachDetachOrder(container, new DefaultWidgetAdder());
}
/**
* Ensures that children are attached and detached in the proper order. This
* must result in the child's onLoad() method being called just *after* its
* element is attached to the DOM, and its onUnload method being called just
* *before* its element is detached from the DOM.
*/
static void testAttachDetachOrder(HasWidgets container, WidgetAdder adder) {
resetContainer(container);
// Make sure the container's attached.
Assert.assertTrue(container instanceof Widget);
RootPanel.get().add((Widget) container);
// Adding and removing the test widget will cause it to test onLoad and
// 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()));
}
/**
* Ensures that the physical and logical state of children are consistent even
* if one of the children throws an error in onLoad.
*
* @param container the container
* @param adder the method of adding children
* @param supportMultipleWidgets true if container supports multiple widgets
*/
static void testDoAttachChildrenWithError(HasWidgets container,
WidgetAdder adder, boolean supportMultipleWidgets) {
resetContainer(container);
// Create a widget that will throw an exception onLoad.
BadWidget badWidget = new BadWidget();
badWidget.setFailOnLoad(true);
// Add some children to a panel.
if (supportMultipleWidgets) {
adder.addChild(container, new Label("test0"));
adder.addChild(container, new Label("test1"));
adder.addChild(container, badWidget);
adder.addChild(container, new Label("test2"));
adder.addChild(container, new Label("test3"));
} else {
adder.addChild(container, badWidget);
}
Assert.assertFalse(badWidget.isAttached());
// Attach the widget.
try {
RootPanel.get().add((Widget) container);
} catch (AttachDetachException e) {
// Expected.
Set<Throwable> causes = e.getCauses();
Assert.assertEquals(1, causes.size());
Throwable[] throwables = causes.toArray(new Throwable[1]);
// Composites that use internal panels for layout (eg. TabPanel) will
// throws the AttachDetachException from the inner panel instead of an
// IllegalArgumentException from the bad widget
Assert.assertTrue(throwables[0] instanceof IllegalArgumentException
|| throwables[0] instanceof AttachDetachException);
}
Iterator<Widget> children = container.iterator();
while (children.hasNext()) {
Widget w = children.next();
Assert.assertTrue(w.isAttached());
assertContainerIsOrHasChild(container, w);
}
Assert.assertEquals(RootPanel.get(), ((Widget) container).getParent());
// Detach the panel.
RootPanel.get().remove((Widget) container);
Assert.assertFalse(badWidget.isAttached());
}
/**
* Ensures that the physical and logical state of children are consistent even
* if one of the children throws an error in onUnload.
*
* @param container the container
* @param adder the method of adding children
* @param supportMultipleWidgets true if container supports multiple widgets
*/
static void testDoDetachChildrenWithError(HasWidgets container,
WidgetAdder adder, boolean supportMultipleWidgets) {
resetContainer(container);
// Create a widget that will throw an exception onUnload.
BadWidget badWidget = new BadWidget();
badWidget.setFailOnUnload(true);
// Add some children to a panel.
if (supportMultipleWidgets) {
adder.addChild(container, new Label("test0"));
adder.addChild(container, new Label("test1"));
adder.addChild(container, badWidget);
adder.addChild(container, new Label("test2"));
adder.addChild(container, new Label("test3"));
} else {
adder.addChild(container, badWidget);
}
Assert.assertFalse(badWidget.isAttached());
// Attach the widget.
RootPanel.get().add((Widget) container);
Assert.assertTrue(badWidget.isAttached());
try {
RootPanel.get().remove((Widget) container);
} catch (AttachDetachException e) {
// Expected.
Set<Throwable> causes = e.getCauses();
Assert.assertEquals(1, causes.size());
Throwable[] throwables = causes.toArray(new Throwable[1]);
// Composites that use internal panels for layout (eg. TabPanel) will
// throws the AttachDetachException from the inner panel instead of an
// IllegalArgumentException from the bad widget
Assert.assertTrue(throwables[0] instanceof IllegalArgumentException
|| throwables[0] instanceof AttachDetachException);
}
Iterator<Widget> children = container.iterator();
while (children.hasNext()) {
Widget w = children.next();
Assert.assertFalse(w.isAttached());
assertContainerIsOrHasChild(container, w);
}
Assert.assertNull(((Widget) container).getParent());
}
/**
* Tests to ensure that {@link HasWidgets#remove(Widget)} is resilient to
* being called with a widget that is not present as a child in the container.
*
* @param container
*/
static void testRemovalOfNonExistantChild(HasWidgets container) {
resetContainer(container);
TestWidget widget = new TestWidget();
container.remove(widget);
}
/**
* Assert that the container is a parent of the child. Some Panels are not the
* direct parent of their children, so we walk up the chain looking for a
* parent.
*
* @param container
* @param child
*/
private static void assertContainerIsOrHasChild(HasWidgets container,
Widget child) {
boolean containerIsOrHasChild = false;
Widget parent = child.getParent();
while (parent != null && !containerIsOrHasChild) {
if (parent == container) {
containerIsOrHasChild = true;
}
parent = parent.getParent();
}
Assert.assertTrue(containerIsOrHasChild);
}
/**
* Reset the container between tests.
*
* @param container the container
*/
private static void resetContainer(HasWidgets container) {
container.clear();
if (((Widget) container).isAttached()) {
RootPanel.get().remove((Widget) container);
}
}
}