Merge up to trunk @r6200.
svn merge --ignore-ancestry -r6142:6200 \
https://google-web-toolkit.googlecode.com/svn/trunk
git-svn-id: https://google-web-toolkit.googlecode.com/svn/branches/farewellSwt@6248 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/branch-info.txt b/branch-info.txt
index 3e4351b..3e01cb9 100644
--- a/branch-info.txt
+++ b/branch-info.txt
@@ -1,9 +1,13 @@
Created from trunk@6101
========================
-svn copy -r6101 https://google-web-toolkit.googlecode.com/svn/trunk \
- https://google-web-toolkit.googlecode.com/svn/branches/farewelSwt
-
+svn copy -r6226 \
+ https://google-web-toolkit.googlecode.com/svn/branches/farewellSwt \
+ https://google-web-toolkit.googlecode.com/svn/changes/jat/noswt-merge6200
Merged from trunk
================
-svn merge https://google-web-toolkit.googlecode.com/svn/trunk -r6101:6142
+svn merge https://google-web-toolkit.googlecode.com/svn/trunk \
+ -r6101:6142
+svn merge --ignore-ancestry -r6142:6200 \
+ https://google-web-toolkit.googlecode.com/svn/trunk
+ (via changes/jat/noswt-merge6200)
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
index 8245f9e..65d375e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
@@ -233,8 +233,16 @@
}
loadIframe("http://google-web-toolkit.googlecode.com/svn/trunk/plugins/MissingBrowserPlugin.html");
} else {
- if (!plugin.connect(url, topWin.__gwt_SessionID, $hosted, $moduleName,
+ if (plugin.connect(url, topWin.__gwt_SessionID, $hosted, $moduleName,
$hostedHtmlVersion)) {
+ window.onUnload = function() {
+ try {
+ // wrap in try/catch since plugins are not required to supply this
+ plugin.disconnect();
+ } catch (e) {
+ }
+ };
+ } else {
if (errFn) {
errFn(modName);
} else {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 28261e8..c37c6c3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -794,8 +794,8 @@
if (resultTypes.size() == 1) {
block.addStmt(entryCalls.get(0).makeStatement());
} else {
- JReboundEntryPoint reboundEntryPoint = new JReboundEntryPoint(null,
- mainType, resultTypes, entryCalls);
+ JReboundEntryPoint reboundEntryPoint = new JReboundEntryPoint(
+ mainType.getSourceInfo(), mainType, resultTypes, entryCalls);
block.addStmt(reboundEntryPoint);
}
}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/HostedModePluginObject.java b/dev/oophm/src/com/google/gwt/dev/shell/HostedModePluginObject.java
index 702d408..01953da 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/HostedModePluginObject.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/HostedModePluginObject.java
@@ -73,6 +73,32 @@
}
/**
+ * Function object which implements the disconnect method on the hosted-mode
+ * plugin.
+ */
+ private class DisconnectMethod extends ScriptableObject implements Function {
+
+ private static final long serialVersionUID = -8799481412144519779L;
+ private static final int EXPECTED_NUM_ARGS = 0;
+
+ public Object call(Context context, Scriptable scope, Scriptable thisObj,
+ Object[] args) {
+ // Allow extra arguments for forward compatibility
+ return disconnect();
+ }
+
+ public Scriptable construct(Context context, Scriptable scope, Object[] args) {
+ throw Context.reportRuntimeError("Function disconnect can't be used as a "
+ + "constructor");
+ }
+
+ @Override
+ public String getClassName() {
+ return "function HostedModePluginObject.disconnect";
+ }
+ }
+
+ /**
* Function object which implements the init method on the hosted-mode plugin.
*/
private class InitMethod extends ScriptableObject implements Function {
@@ -111,9 +137,12 @@
private static final long serialVersionUID = -1815031145376726799L;
private Scriptable connectMethod;
+ private Scriptable disconnectMethod;
private Scriptable initMethod;
private Window window;
+ private BrowserChannelClient browserChannelClient;
+
/**
* Initiate a hosted mode connection to the requested port and load the
* requested module.
@@ -138,7 +167,7 @@
try {
HtmlUnitSessionHandler htmlUnitSessionHandler = new HtmlUnitSessionHandler(window);
- BrowserChannelClient browserChannelClient = new BrowserChannelClient(
+ browserChannelClient = new BrowserChannelClient(
addressParts, url, sessionKey, module, version,
htmlUnitSessionHandler);
htmlUnitSessionHandler.setSessionData(new SessionData(
@@ -153,6 +182,14 @@
}
}
+ public boolean disconnect() {
+ try {
+ return browserChannelClient.disconnectFromHost();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
@Override
public Object get(String name, Scriptable start) {
if ("connect".equals(name)) {
@@ -160,6 +197,11 @@
connectMethod = new ConnectMethod();
}
return connectMethod;
+ } else if ("disconnect".equals(name)) {
+ if (disconnectMethod == null) {
+ disconnectMethod = new DisconnectMethod();
+ }
+ return disconnectMethod;
} else if ("init".equals(name)) {
if (initMethod == null) {
initMethod = new InitMethod();
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml
new file mode 100644
index 0000000..d83fadc
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml
@@ -0,0 +1,28 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+<module rename-to="issue3892Module1">
+
+ <!-- Inherit the core Web Toolkit stuff. -->
+ <inherits name='com.google.gwt.user.User' />
+ <inherits name='com.google.gwt.museum.Museum' />
+ <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+ <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+ <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+ <!-- Specify the app entry point class. -->
+ <entry-point class='com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint1' />
+ <source path="client/common" />
+ <source path="client/defaultmuseum" />
+ <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml
new file mode 100644
index 0000000..47fb0eb
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml
@@ -0,0 +1,28 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+<module rename-to="issue3892Module2">
+
+ <!-- Inherit the core Web Toolkit stuff. -->
+ <inherits name='com.google.gwt.user.User' />
+ <inherits name='com.google.gwt.museum.Museum' />
+ <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+ <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+ <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+ <!-- Specify the app entry point class. -->
+ <entry-point class="com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint2" />
+ <source path="client/common" />
+ <source path="client/defaultmuseum" />
+ <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml
new file mode 100644
index 0000000..0b42303
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml
@@ -0,0 +1,28 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+<module rename-to="issue3892Module3">
+
+ <!-- Inherit the core Web Toolkit stuff. -->
+ <inherits name='com.google.gwt.user.User' />
+ <inherits name='com.google.gwt.museum.Museum' />
+ <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+ <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+ <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+ <!-- Specify the app entry point class. -->
+ <entry-point class="com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint3" />
+ <source path="client/common" />
+ <source path="client/defaultmuseum" />
+ <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java
new file mode 100644
index 0000000..dd3b799
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint1 extends AbstractIssue {
+
+ public static final String BUTTON_1_ID = "Issue3892Button1";
+ public static final String BUTTON_2_ID = "Issue3892Button2";
+ public static final String BUTTON_3_ID = "Issue3892Button3";
+
+ /**
+ * The main grid used for layout.
+ */
+ private Grid grid = new Grid(1, 3);
+
+ @Override
+ public Widget createIssue() {
+ Window.alert("Module 1 loaded");
+
+ // Setup the grid.
+ grid.setHTML(0, 0, "<b>Test<b>");
+ grid.setHTML(0, 1, "<b>Description<b>");
+ grid.setHTML(0, 2, "<b>Expected Results<b>");
+ addTest(BUTTON_1_ID, "Event is not cancelled by any module.",
+ "The event will fire in the button.", false);
+ addTest(BUTTON_2_ID, "Module 1 cancels event.",
+ "The event will not fire in the button.", true);
+ addTest(BUTTON_3_ID, "Module 2 cancels event.",
+ "The event will not fire in the button.", true);
+
+ // Add the event preview.
+ Event.addNativePreviewHandler(new NativePreviewHandler() {
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ Element target = event.getNativeEvent().getEventTarget().cast();
+ if (BUTTON_2_ID.equals(target.getId())) {
+ event.cancel();
+ Window.alert("Click handled by module 1 and cancelled");
+ } else {
+ Window.alert("Click handled by module 1");
+ }
+ }
+ }
+ });
+
+ return grid;
+ }
+
+ @Override
+ public String getInstructions() {
+ return "After all three modules have loaded (indicated by alert boxes), "
+ + "click the buttons and verify that you see the expected results. "
+ + "For each test, all three modules should preview the event (even if "
+ + "one of the modules cancels the event).";
+ }
+
+ @Override
+ public String getSummary() {
+ return "Only a single GWT application can preview native events";
+ }
+
+ @Override
+ public boolean hasCSS() {
+ return false;
+ }
+
+ /**
+ * Add a test button to the grid.
+ *
+ * @param buttonId the ID of the button
+ * @param description the test description
+ * @param results the expected result of the test
+ * @param isCancelled true if one of the modules will cancel the event
+ */
+ private void addTest(String buttonId, String description, String results,
+ final boolean isCancelled) {
+ int row = grid.getRowCount();
+ grid.resizeRows(row + 1);
+
+ // Add the test button.
+ Button button = new Button("Run Test", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ if (isCancelled) {
+ Window.alert("[Error] Event should have been cancelled");
+ } else {
+ Window.alert("[Success] Event successfully fired");
+ }
+ }
+ });
+ button.getElement().setId(buttonId);
+ grid.setWidget(row, 0, button);
+
+ // Add the description and expected results.
+ grid.setText(row, 1, description);
+ grid.setText(row, 2, results);
+ }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java
new file mode 100644
index 0000000..1231698
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java
@@ -0,0 +1,45 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint2 implements EntryPoint {
+ public void onModuleLoad() {
+ Window.alert("Module 2 loaded");
+ Event.addNativePreviewHandler(new NativePreviewHandler() {
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ Element target = event.getNativeEvent().getEventTarget().cast();
+ if (Issue3892EntryPoint1.BUTTON_3_ID.equals(target.getId())) {
+ event.cancel();
+ Window.alert("Click handled by module 2 and cancelled");
+ } else {
+ Window.alert("Click handled by module 2");
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java
new file mode 100644
index 0000000..4f52d40
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java
@@ -0,0 +1,38 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint3 implements EntryPoint {
+ public void onModuleLoad() {
+ Window.alert("Module 3 loaded");
+ Event.addNativePreviewHandler(new NativePreviewHandler() {
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ Window.alert("Click handled by module 3");
+ }
+ }
+ });
+ }
+}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml b/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
index 6cca2a8..9225cee 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
+++ b/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
@@ -14,5 +14,6 @@
<module rename-to="mail">
<inherits name='com.google.gwt.user.User'/>
+ <inherits name="com.google.gwt.uibinder.UiBinder" />
<entry-point class='com.google.gwt.sample.mail.client.Mail'/>
</module>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
index f371ee0..12d1e62 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
@@ -15,57 +15,53 @@
*/
package com.google.gwt.sample.mail.client;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
/**
* A simple example of an 'about' dialog box.
*/
public class AboutDialog extends DialogBox {
+ interface Binder extends UiBinder<Widget, AboutDialog> { }
+ private static final Binder binder = GWT.create(Binder.class);
+
+ @UiField Button closeButton;
+
public AboutDialog() {
// Use this opportunity to set the dialog's caption.
setText("About the Mail Sample");
-
- // Create a VerticalPanel to contain the 'about' label and the 'OK' button.
- VerticalPanel outer = new VerticalPanel();
-
- // Create the 'about' text and set a style name so we can style it with CSS.
-
- HTML text = new HTML("This sample application demonstrates the "
- + "construction of a complex user interface using GWT's built-in "
- + "widgets. Have a look at the code to see how easy it is to build "
- + "your own apps!");
- text.setStyleName("mail-AboutText");
- outer.add(text);
-
- // Create the 'OK' button, along with a handler that hides the dialog
- // when the button is clicked.
- outer.add(new Button("Close", new ClickHandler() {
- public void onClick(ClickEvent event) {
- hide();
- }
- }));
-
- setWidget(outer);
+ setWidget(binder.createAndBindUi(this));
}
@Override
- public boolean onKeyDownPreview(char key, int modifiers) {
- // Use the popup's key preview hooks to close the dialog when either
- // enter or escape is pressed.
- switch (key) {
- case KeyCodes.KEY_ENTER:
- case KeyCodes.KEY_ESCAPE:
- hide();
- break;
- }
+ protected void onPreviewNativeEvent(NativePreviewEvent preview) {
+ super.onPreviewNativeEvent(preview);
- return true;
+ NativeEvent evt = preview.getNativeEvent();
+ if (evt.getType().equals("keydown")) {
+ // Use the popup's key preview hooks to close the dialog when either
+ // enter or escape is pressed.
+ switch (evt.getKeyCode()) {
+ case KeyCodes.KEY_ENTER:
+ case KeyCodes.KEY_ESCAPE:
+ hide();
+ break;
+ }
+ }
+ }
+
+ @UiHandler("closeButton")
+ void onSignOutClicked(ClickEvent event) {
+ hide();
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml
new file mode 100644
index 0000000..3aa9afc
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml
@@ -0,0 +1,27 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .aboutText {
+ width: 24em;
+ padding: 10px;
+ text-align: left;
+ }
+ </ui:style>
+
+ <g:DockPanel>
+ <g:Dock direction='SOUTH'>
+ <g:Button text='Close' ui:field='closeButton' />
+ </g:Dock>
+ <g:Dock direction='CENTER'>
+ <g:HTML styleName='{style.aboutText}'>
+ This sample application demonstrates the
+ construction of a complex user interface using GWT's built-in
+ widgets. Have a look at the code to see how easy it is to build
+ your own apps!
+ </g:HTML>
+ </g:Dock>
+ </g:DockPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml
new file mode 100644
index 0000000..fc5b2a0
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml
@@ -0,0 +1,41 @@
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .popup {
+ background: #fff;
+ border: 1px solid #666;
+ padding: 0.5em;
+ width: 14em;
+ height: 2.5em;
+ }
+
+ .photo {
+ float: left;
+ margin-right: 4px;
+
+ background: url(default_photo.jpg);
+ width: 32px;
+ height: 32px;
+ }
+
+ .right {
+ white-space: nowrap;
+ }
+
+ .email {
+ font-style:italic;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName='{style.popup}'>
+ <div class='{style.photo}'/>
+ <div class='{style.right}'>
+ <div ui:field='nameDiv' />
+ <div ui:field='emailDiv' class='{style.email}'/>
+ </div>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
index 738b1fb..df69bdf 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
@@ -15,17 +15,18 @@
*/
package com.google.gwt.sample.mail.client;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.ImageBundle;
-import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
/**
* A component that displays a list of contacts.
@@ -33,14 +34,6 @@
public class Contacts extends Composite {
/**
- * An image bundle for this widget and an example of the use of @Resource.
- */
- public interface Images extends ImageBundle {
- @Resource("default_photo.jpg")
- AbstractImagePrototype defaultPhoto();
- }
-
- /**
* Simple data structure representing a contact.
*/
private static class Contact {
@@ -56,31 +49,28 @@
/**
* A simple popup that displays a contact's information.
*/
- private class ContactPopup extends PopupPanel {
+ static class ContactPopup extends PopupPanel {
+ @UiTemplate("ContactPopup.ui.xml")
+ interface Binder extends UiBinder<Widget, ContactPopup> { }
+ private static final Binder binder = GWT.create(Binder.class);
+
+ @UiField Element nameDiv;
+ @UiField Element emailDiv;
public ContactPopup(Contact contact) {
// The popup's constructor's argument is a boolean specifying that it
// auto-close itself when the user clicks outside of it.
super(true);
+ add(binder.createAndBindUi(this));
- VerticalPanel inner = new VerticalPanel();
- Label nameLabel = new Label(contact.name);
- Label emailLabel = new Label(contact.email);
- inner.add(nameLabel);
- inner.add(emailLabel);
-
- HorizontalPanel hp = new HorizontalPanel();
- hp.setSpacing(4);
- hp.add(images.defaultPhoto().createImage());
- hp.add(inner);
-
- add(hp);
- setStyleName("mail-ContactPopup");
- nameLabel.setStyleName("mail-ContactPopupName");
- emailLabel.setStyleName("mail-ContactPopupEmail");
+ nameDiv.setInnerText(contact.name);
+ emailDiv.setInnerText(contact.email);
}
}
+ interface Binder extends UiBinder<VerticalPanel, Contacts> { }
+ private static final Binder binder = GWT.create(Binder.class);
+
private Contact[] contacts = new Contact[] {
new Contact("Benoit Mandelbrot", "benoit@example.com"),
new Contact("Albert Einstein", "albert@example.com"),
@@ -91,26 +81,19 @@
new Contact("Alan Turing", "alan@example.com"),
new Contact("John von Neumann", "john@example.com")};
- private VerticalPanel panel = new VerticalPanel();
- private final Images images;
+ private VerticalPanel panel;
- public Contacts(Images images) {
- SimplePanel outer = new SimplePanel();
- outer.setWidget(panel);
+ public Contacts() {
+ initWidget(panel = binder.createAndBindUi(this));
- this.images = images;
// Add all the contacts to the list.
for (int i = 0; i < contacts.length; ++i) {
addContact(contacts[i]);
}
-
- initWidget(outer);
- setStyleName("mail-Contacts");
}
private void addContact(final Contact contact) {
- final HTML link = new HTML("<a href='javascript:;'>" + contact.name
- + "</a>");
+ final Anchor link = new Anchor(contact.name);
panel.add(link);
// Add a click handler that displays a ContactPopup when it is clicked.
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml
new file mode 100644
index 0000000..5349613
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml
@@ -0,0 +1,13 @@
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .contacts {
+ padding: 0.5em;
+ }
+ </ui:style>
+
+ <g:VerticalPanel styleName='{style.contacts}'/>
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
index 1b7f652..a639a12 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
@@ -17,128 +17,58 @@
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.DockPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
/**
* This application demonstrates how to construct a relatively complex user
* interface, similar to many common email readers. It has no back-end,
* populating its components with hard-coded data.
*/
-public class Mail implements EntryPoint, ResizeHandler {
+public class Mail implements EntryPoint {
- private static Mail singleton;
+ interface Binder extends UiBinder<DockLayoutPanel, Mail> { }
+ private static final Binder binder = GWT.create(Binder.class);
- /**
- * Instantiate an application-level image bundle. This object will provide
- * programmatic access to all the images needed by widgets.
- */
- private static final Images images = GWT.create(Images.class);
-
- /**
- * An aggregate image bundle that pulls together all the images for this
- * application into a single bundle.
- */
- public interface Images extends Shortcuts.Images, TopPanel.Images {
- }
-
- /**
- * Gets the singleton Mail instance.
- */
- public static Mail get() {
- return singleton;
- }
-
- private TopPanel topPanel = new TopPanel(images);
- private VerticalPanel rightPanel = new VerticalPanel();
- private MailList mailList;
- private MailDetail mailDetail = new MailDetail();
- private Shortcuts shortcuts = new Shortcuts(images);
-
- /**
- * Displays the specified item.
- *
- * @param item
- */
- public void displayItem(MailItem item) {
- mailDetail.setItem(item);
- }
+ @UiField TopPanel topPanel;
+ @UiField MailList mailList;
+ @UiField MailDetail mailDetail;
+ @UiField Shortcuts shortcuts;
/**
* This method constructs the application user interface by instantiating
* controls and hooking up event handler.
*/
public void onModuleLoad() {
- singleton = this;
-
- topPanel.setWidth("100%");
-
- // MailList uses Mail.get() in its constructor, so initialize it after
- // 'singleton'.
- mailList = new MailList();
- mailList.setWidth("100%");
-
- // Create the right panel, containing the email list & details.
- rightPanel.add(mailList);
- rightPanel.add(mailDetail);
- mailList.setWidth("100%");
- mailDetail.setWidth("100%");
-
- // Create a dock panel that will contain the menu bar at the top,
- // the shortcuts to the left, and the mail list & details taking the rest.
- DockPanel outer = new DockPanel();
- outer.add(topPanel, DockPanel.NORTH);
- outer.add(shortcuts, DockPanel.WEST);
- outer.add(rightPanel, DockPanel.CENTER);
- outer.setWidth("100%");
-
- outer.setSpacing(4);
- outer.setCellWidth(rightPanel, "100%");
-
- // Hook the window resize event, so that we can adjust the UI.
- Window.addResizeHandler(this);
+ DockLayoutPanel outer = binder.createAndBindUi(this);
// Get rid of scrollbars, and clear out the window's built-in margin,
// because we want to take advantage of the entire client area.
Window.enableScrolling(false);
Window.setMargin("0px");
- // Finally, add the outer panel to the RootPanel, so that it will be
- // displayed.
- RootPanel.get().add(outer);
+ // Special-case stuff to make topPanel overhang a bit.
+ Element topElem = outer.getContainerElementFor(topPanel);
+ topElem.getStyle().setZIndex(2);
+ topElem.getStyle().setOverflow(Overflow.VISIBLE);
- // Call the window resized handler to get the initial sizes setup. Doing
- // this in a deferred command causes it to occur after all widgets' sizes
- // have been computed by the browser.
- DeferredCommand.addCommand(new Command() {
- public void execute() {
- onWindowResized(Window.getClientWidth(), Window.getClientHeight());
+ // Listen for item selection, displaying the currently-selected item in
+ // the detail area.
+ mailList.setListener(new MailList.Listener() {
+ public void onItemSelected(MailItem item) {
+ mailDetail.setItem(item);
}
});
- onWindowResized(Window.getClientWidth(), Window.getClientHeight());
- }
-
- public void onResize(ResizeEvent event) {
- onWindowResized(event.getWidth(), event.getHeight());
- }
-
- public void onWindowResized(int width, int height) {
- // Adjust the shortcut panel and detail area to take up the available room
- // in the window.
- int shortcutHeight = height - shortcuts.getAbsoluteTop() - 8;
- if (shortcutHeight < 1) {
- shortcutHeight = 1;
- }
- shortcuts.setHeight(shortcutHeight + "px");
-
- // Give the mail detail widget a chance to resize itself as well.
- mailDetail.adjustSize(width, height);
+ // Add the outer panel to the RootLayoutPanel, so that it will be
+ // displayed.
+ RootLayoutPanel root = RootLayoutPanel.get();
+ root.add(outer);
+ root.layout();
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml
new file mode 100644
index 0000000..08abf6f
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml
@@ -0,0 +1,28 @@
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <g:DockLayoutPanel unit='EM'>
+ <g:north size='5'>
+ <mail:TopPanel ui:field='topPanel' />
+ </g:north>
+
+ <g:center>
+ <g:SplitLayoutPanel>
+ <g:west size='192'>
+ <mail:Shortcuts ui:field='shortcuts' />
+ </g:west>
+
+ <g:north size='200'>
+ <mail:MailList ui:field='mailList' />
+ </g:north>
+
+ <g:center>
+ <mail:MailDetail ui:field='mailDetail' />
+ </g:center>
+ </g:SplitLayoutPanel>
+ </g:center>
+ </g:DockLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
index 42901ba..b8697ec 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
@@ -15,74 +15,39 @@
*/
package com.google.gwt.sample.mail.client;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.Widget;
/**
* A composite for displaying the details of an email message.
*/
-public class MailDetail extends Composite {
+public class MailDetail extends LayoutComposite {
- private VerticalPanel panel = new VerticalPanel();
- private VerticalPanel headerPanel = new VerticalPanel();
- private HTML subject = new HTML();
- private HTML sender = new HTML();
- private HTML recipient = new HTML();
- private HTML body = new HTML();
- private ScrollPanel scroller = new ScrollPanel(body);
+ interface Binder extends UiBinder<Widget, MailDetail> { }
+ private static final Binder binder = GWT.create(Binder.class);
+
+ @UiField Element subject;
+ @UiField Element sender;
+ @UiField Element recipient;
+ @UiField HTML body;
public MailDetail() {
- body.setWordWrap(true);
-
- headerPanel.add(subject);
- headerPanel.add(sender);
- headerPanel.add(recipient);
- headerPanel.setWidth("100%");
-
- DockPanel innerPanel = new DockPanel();
- innerPanel.add(headerPanel, DockPanel.NORTH);
- innerPanel.add(scroller, DockPanel.CENTER);
-
- innerPanel.setCellHeight(scroller, "100%");
- panel.add(innerPanel);
- innerPanel.setSize("100%", "100%");
- scroller.setSize("100%", "100%");
- initWidget(panel);
-
- setStyleName("mail-Detail");
- headerPanel.setStyleName("mail-DetailHeader");
- innerPanel.setStyleName("mail-DetailInner");
- subject.setStyleName("mail-DetailSubject");
- sender.setStyleName("mail-DetailSender");
- recipient.setStyleName("mail-DetailRecipient");
- body.setStyleName("mail-DetailBody");
- }
-
- /**
- * Adjusts the widget's size such that it fits within the window's client
- * area.
- */
- public void adjustSize(int windowWidth, int windowHeight) {
- int scrollWidth = windowWidth - scroller.getAbsoluteLeft() - 9;
- if (scrollWidth < 1) {
- scrollWidth = 1;
- }
-
- int scrollHeight = windowHeight - scroller.getAbsoluteTop() - 9;
- if (scrollHeight < 1) {
- scrollHeight = 1;
- }
-
- scroller.setPixelSize(scrollWidth, scrollHeight);
+ initWidget(binder.createAndBindUi(this));
}
public void setItem(MailItem item) {
- subject.setHTML(item.subject);
- sender.setHTML("<b>From:</b> " + item.sender);
- recipient.setHTML("<b>To:</b> foo@example.com");
+ subject.setInnerText(item.subject);
+ sender.setInnerText(item.sender);
+ recipient.setInnerHTML("foo@example.com");
+
+ // WARNING: For the purposes of this demo, we're using HTML directly, on
+ // the assumption that the "server" would have appropriately scrubbed the
+ // HTML. Failure to do so would open your application to XSS attacks.
body.setHTML(item.body);
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml
new file mode 100644
index 0000000..d5872ff
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml
@@ -0,0 +1,44 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .detail {
+ border: 1px solid #666;
+ background-color: white;
+ }
+
+ .header {
+ background: #eee;
+ border-bottom: 1px solid #666;
+ padding: 0.5em;
+ }
+
+ .headerItem {
+ margin-bottom:0.5em;
+ }
+
+ .body {
+ line-height: 150%;
+ padding: 20px 40px 20px 10px;
+ font-family: 'Times New Roman', Times, serif;
+ }
+ </ui:style>
+
+ <g:DockLayoutPanel unit='EM' styleName='{style.detail}'>
+ <g:north size='6'>
+ <g:HTMLPanel styleName='{style.header}'>
+ <div class='{style.headerItem}' ui:field='subject'/>
+ <div class='{style.headerItem}'><b>From:</b> <span ui:field='sender'/></div>
+ <div class='{style.headerItem}'><b>To:</b> <span ui:field='recipient'/></div>
+ </g:HTMLPanel>
+ </g:north>
+
+ <g:center>
+ <g:ScrollPanel>
+ <g:HTML styleName='{style.body}' ui:field='body' wordWrap='true'/>
+ </g:ScrollPanel>
+ </g:center>
+ </g:DockLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
index d36b789..3b4828c 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
@@ -22,7 +22,7 @@
*/
public class MailItems {
- private static final int NUM_ITEMS = 37;
+ private static final int NUM_ITEMS = 64;
private static final int FRAGMENTS_PER_EMAIL = 10;
private static final String[] senders = new String[] {
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
index 43fa86a..b02edc8 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
@@ -15,20 +15,32 @@
*/
package com.google.gwt.sample.mail.client;
+import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
/**
* A composite that displays a list of emails that can be selected.
*/
-public class MailList extends Composite implements ClickHandler {
+public class MailList extends LayoutComposite implements ClickHandler {
- private static final int VISIBLE_EMAIL_COUNT = 10;
+ private static final int VISIBLE_EMAIL_COUNT = 20;
+
+ /**
+ * Callback when mail items are selected.
+ */
+ public interface Listener {
+ void onItemSelected(MailItem item);
+ }
+
+ private Listener listener;
private HTML countLabel = new HTML();
private HTML newerButton = new HTML("<a href='javascript:;'>< newer</a>",
@@ -36,6 +48,7 @@
private HTML olderButton = new HTML("<a href='javascript:;'>older ></a>",
true);
private int startIndex, selectedRow = -1;
+ private FlexTable header = new FlexTable();
private FlexTable table = new FlexTable();
private HorizontalPanel navBar = new HorizontalPanel();
@@ -61,7 +74,13 @@
navBar.add(innerNavBar);
navBar.setWidth("100%");
- initWidget(table);
+ DockLayoutPanel dock = new DockLayoutPanel(Unit.EM);
+ dock.addNorth(header, 2);
+ dock.add(new ScrollPanel(table));
+ header.setWidth("100%");
+ table.setWidth("100%");
+ dock.layout();
+ initWidget(dock);
setStyleName("mail-List");
initTable();
@@ -95,34 +114,60 @@
Cell cell = table.getCellForEvent(event);
if (cell != null) {
int row = cell.getRowIndex();
- if (row > 0) {
- selectRow(row - 1);
- }
+ selectRow(row);
}
}
}
/**
+ * Sets the listener that will be notified when an item is selected.
+ */
+ public void setListener(Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ protected void onLoad() {
+ // Select the first row if none is selected.
+ if (selectedRow == -1) {
+ selectRow(0);
+ }
+ }
+
+ /**
* Initializes the table so that it contains enough rows for a full page of
* emails. Also creates the images that will be used as 'read' flags.
*/
private void initTable() {
- // Create the header row.
- table.setText(0, 0, "Sender");
- table.setText(0, 1, "Email");
- table.setText(0, 2, "Subject");
- table.setWidget(0, 3, navBar);
- table.getRowFormatter().setStyleName(0, "mail-ListHeader");
+ // Create the header.
+ header.getRowFormatter().setStyleName(0, "mail-ListHeader");
+ header.getElement().getStyle().setProperty("tableLayout", "fixed");
+ header.setCellSpacing(0);
- // Initialize the rest of the rows.
+ header.getColumnFormatter().setWidth(0, "96px");
+ header.getColumnFormatter().setWidth(1, "160px");
+ header.getColumnFormatter().setWidth(3, "256px");
+
+ header.setText(0, 0, "Sender");
+ header.setText(0, 1, "Email");
+ header.setText(0, 2, "Subject");
+ header.setWidget(0, 3, navBar);
+
+ // Initialize the table.
+ table.getElement().getStyle().setProperty("tableLayout", "fixed");
+ header.setCellSpacing(0);
+
+ table.getColumnFormatter().setWidth(0, "96px");
+ table.getColumnFormatter().setWidth(1, "160px");
+
for (int i = 0; i < VISIBLE_EMAIL_COUNT; ++i) {
- table.setText(i + 1, 0, "");
- table.setText(i + 1, 1, "");
- table.setText(i + 1, 2, "");
- table.getCellFormatter().setWordWrap(i + 1, 0, false);
- table.getCellFormatter().setWordWrap(i + 1, 1, false);
- table.getCellFormatter().setWordWrap(i + 1, 2, false);
- table.getFlexCellFormatter().setColSpan(i + 1, 2, 2);
+ table.setText(i, 0, "");
+ table.setText(i, 1, "");
+ table.setText(i, 2, "");
+ table.getCellFormatter().setWordWrap(i, 0, false);
+ table.getCellFormatter().setWordWrap(i, 1, false);
+ table.getCellFormatter().setWordWrap(i, 2, false);
+ table.getFlexCellFormatter().setColSpan(i, 2, 2);
}
}
@@ -144,15 +189,18 @@
item.read = true;
selectedRow = row;
- Mail.get().displayItem(item);
+
+ if (listener != null) {
+ listener.onItemSelected(item);
+ }
}
private void styleRow(int row, boolean selected) {
if (row != -1) {
if (selected) {
- table.getRowFormatter().addStyleName(row + 1, "mail-SelectedRow");
+ table.getRowFormatter().addStyleName(row, "mail-SelectedRow");
} else {
- table.getRowFormatter().removeStyleName(row + 1, "mail-SelectedRow");
+ table.getRowFormatter().removeStyleName(row, "mail-SelectedRow");
}
}
}
@@ -181,21 +229,16 @@
// Add a new row to the table, then set each of its columns to the
// email's sender and subject values.
- table.setText(i + 1, 0, item.sender);
- table.setText(i + 1, 1, item.email);
- table.setText(i + 1, 2, item.subject);
+ table.setText(i, 0, item.sender);
+ table.setText(i, 1, item.email);
+ table.setText(i, 2, item.subject);
}
// Clear any remaining slots.
for (; i < VISIBLE_EMAIL_COUNT; ++i) {
- table.setHTML(i + 1, 0, " ");
- table.setHTML(i + 1, 1, " ");
- table.setHTML(i + 1, 2, " ");
- }
-
- // Select the first row if none is selected.
- if (selectedRow == -1) {
- selectRow(0);
+ table.setHTML(i, 0, " ");
+ table.setHTML(i, 1, " ");
+ table.setHTML(i, 2, " ");
}
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java b/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
index f0e852d..27fb3c5 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.sample.mail.client;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ImageBundle;
@@ -55,7 +56,9 @@
*
* @param images a bundle that provides the images for this widget
*/
- public Mailboxes(Images images) {
+ public Mailboxes() {
+ Images images = GWT.create(Images.class);
+
tree = new Tree(images);
TreeItem root = new TreeItem(
imageItemHTML(images.home(), "foo@example.com"));
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
index c58b71a..28687a1 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
@@ -15,10 +15,11 @@
*/
package com.google.gwt.sample.mail.client;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DecoratedStackPanel;
-import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
/**
* A composite that contains the shortcut stack panel on the left side. The
@@ -27,67 +28,29 @@
* {@link com.google.gwt.user.client.ui.StackPanel},
* {@link com.google.gwt.user.client.ui.Tree}, and other custom widgets.
*/
-public class Shortcuts extends Composite {
+public class Shortcuts extends LayoutComposite {
- /**
- * An image bundle specifying the images for this Widget and aggragating
- * images needed in child widgets.
- */
- public interface Images extends Contacts.Images, Mailboxes.Images {
- AbstractImagePrototype contactsgroup();
+ interface Binder extends UiBinder<StackLayoutPanel, Shortcuts> { }
+ private static final Binder binder = GWT.create(Binder.class);
- AbstractImagePrototype mailgroup();
+ private StackLayoutPanel stackPanel;
- AbstractImagePrototype tasksgroup();
- }
-
- private int nextHeaderIndex = 0;
- private DecoratedStackPanel stackPanel = new DecoratedStackPanel();
+ @UiField Mailboxes mailboxes;
+ @UiField Tasks tasks;
+ @UiField Contacts contacts;
/**
* Constructs a new shortcuts widget using the specified images.
*
* @param images a bundle that provides the images for this widget
*/
- public Shortcuts(Images images) {
- // Create the groups within the stack panel.
- add(new Mailboxes(images), images.mailgroup(), "Mail");
- add(new Tasks(), images.tasksgroup(), "Tasks");
- add(new Contacts(images), images.contactsgroup(), "Contacts");
-
- initWidget(stackPanel);
+ public Shortcuts() {
+ initWidget(stackPanel = binder.createAndBindUi(this));
}
@Override
protected void onLoad() {
// Show the mailboxes group by default.
- stackPanel.showStack(0);
- }
-
- private void add(Widget widget, AbstractImagePrototype imageProto,
- String caption) {
- widget.addStyleName("mail-StackContent");
- stackPanel.add(widget, createHeaderHTML(imageProto, caption), true);
- }
-
- /**
- * Creates an HTML fragment that places an image & caption together, for use
- * in a group header.
- *
- * @param imageProto an image prototype for an image
- * @param caption the group caption
- * @return the header HTML fragment
- */
- private String createHeaderHTML(AbstractImagePrototype imageProto,
- String caption) {
- nextHeaderIndex++;
-
- String captionHTML = "<table class='caption' cellpadding='0' cellspacing='0'>"
- + "<tr><td class='lcaption'>"
- + imageProto.getHTML()
- + "</td><td class='rcaption'><b style='white-space:nowrap'>"
- + caption
- + "</b></td></tr></table>";
- return captionHTML;
+ stackPanel.showWidget(mailboxes);
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml
new file mode 100644
index 0000000..2cfa4eb
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml
@@ -0,0 +1,45 @@
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .shortcuts {
+ border-left: 1px solid #666;
+ border-right: 1px solid #666;
+ }
+
+ .stackHeader {
+ background: #c1eec8 url(gradient.gif) repeat-x 0px -1px;
+ padding-top: 1em;
+ font-weight: bold;
+ text-align: center;
+ border-top: 1px solid #666;
+ border-bottom: 1px solid #666;
+ }
+ </ui:style>
+
+ <g:StackLayoutPanel styleName='{style.shortcuts}' unit='EM'>
+ <g:stack>
+ <g:header size='4'>
+ <g:Label styleName='{style.stackHeader}'>Mailboxes</g:Label>
+ </g:header>
+ <mail:Mailboxes ui:field='mailboxes'/>
+ </g:stack>
+
+ <g:stack>
+ <g:header size='4'>
+ <g:Label styleName='{style.stackHeader}'>Tasks</g:Label>
+ </g:header>
+ <mail:Tasks ui:field='tasks'/>
+ </g:stack>
+
+ <g:stack>
+ <g:header size='4'>
+ <g:Label styleName='{style.stackHeader}'>Contacts</g:Label>
+ </g:header>
+ <mail:Contacts ui:field='contacts'/>
+ </g:stack>
+ </g:StackLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java b/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
index b8e79d4..73a4fe7 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
@@ -17,7 +17,6 @@
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
/**
@@ -26,17 +25,14 @@
public class Tasks extends Composite {
public Tasks() {
- SimplePanel panel = new SimplePanel();
VerticalPanel list = new VerticalPanel();
- panel.setWidget(list);
list.add(new CheckBox("Get groceries"));
list.add(new CheckBox("Walk the dog"));
list.add(new CheckBox("Start Web 2.0 company"));
list.add(new CheckBox("Write cool app in GWT"));
list.add(new CheckBox("Get funding"));
list.add(new CheckBox("Take a vacation"));
- initWidget(panel);
+ initWidget(list);
setStyleName("mail-Tasks");
}
-
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
index 0d6d178..ad5bc84 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
@@ -15,71 +15,43 @@
*/
package com.google.gwt.sample.mail.client;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.ImageBundle;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
/**
* The top panel, which contains the 'welcome' message and various links.
*/
-public class TopPanel extends Composite implements ClickHandler {
+public class TopPanel extends Composite {
- /**
- * An image bundle for this widgets images.
- */
- public interface Images extends ImageBundle {
- AbstractImagePrototype logo();
+ interface Binder extends UiBinder<Widget, TopPanel> { }
+ private static final Binder binder = GWT.create(Binder.class);
+
+ @UiField Anchor signOutLink;
+ @UiField Anchor aboutLink;
+
+ public TopPanel() {
+ initWidget(binder.createAndBindUi(this));
}
- private HTML signOutLink = new HTML("<a href='javascript:;'>Sign Out</a>");
- private HTML aboutLink = new HTML("<a href='javascript:;'>About</a>");
-
- public TopPanel(Images images) {
- HorizontalPanel outer = new HorizontalPanel();
- VerticalPanel inner = new VerticalPanel();
-
- outer.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
- inner.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
-
- HorizontalPanel links = new HorizontalPanel();
- links.setSpacing(4);
- links.add(signOutLink);
- links.add(aboutLink);
-
- final Image logo = images.logo().createImage();
- outer.add(logo);
- outer.setCellHorizontalAlignment(logo, HorizontalPanel.ALIGN_LEFT);
-
- outer.add(inner);
- inner.add(new HTML("<b>Welcome back, foo@example.com</b>"));
- inner.add(links);
-
- signOutLink.addClickHandler(this);
- aboutLink.addClickHandler(this);
-
- initWidget(outer);
- setStyleName("mail-TopPanel");
- links.setStyleName("mail-TopPanelLinks");
+ @UiHandler("aboutLink")
+ void onAboutClicked(ClickEvent event) {
+ // When the 'About' item is selected, show the AboutDialog.
+ // Note that showing a dialog box does not block -- execution continues
+ // normally, and the dialog fires an event when it is closed.
+ AboutDialog dlg = new AboutDialog();
+ dlg.show();
+ dlg.center();
}
- public void onClick(ClickEvent event) {
- Object sender = event.getSource();
- if (sender == signOutLink) {
- Window.alert("If this were implemented, you would be signed out now.");
- } else if (sender == aboutLink) {
- // When the 'About' item is selected, show the AboutDialog.
- // Note that showing a dialog box does not block -- execution continues
- // normally, and the dialog fires an event when it is closed.
- AboutDialog dlg = new AboutDialog();
- dlg.show();
- dlg.center();
- }
+ @UiHandler("signOutLink")
+ void onSignOutClicked(ClickEvent event) {
+ Window.alert("If this were implemented, you would be signed out now.");
}
}
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml
new file mode 100644
index 0000000..1981a59
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml
@@ -0,0 +1,39 @@
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+ <ui:style>
+ .statusDiv {
+ text-align: right;
+ margin: 1em;
+ }
+
+ .linksDiv {
+ text-align: right;
+ }
+
+ .logo {
+ background: url(logo.png);
+ position: absolute;
+ width: 140px;
+ height: 75px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel>
+ <div class='{style.logo}'/>
+
+ <div class="{style.statusDiv}">
+ <div>
+ <b>Welcome back, foo@example.com</b>
+ </div>
+
+ <div class='{style.linksDiv}'>
+ <g:Anchor href='javascript:;' ui:field='signOutLink'>Sign Out</g:Anchor>
+ <g:Anchor href='javascript:;' ui:field='aboutLink'>About</g:Anchor>
+ </div>
+ </div>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/war/Mail.css b/samples/mail/war/Mail.css
index 2d045a4..023ced9 100644
--- a/samples/mail/war/Mail.css
+++ b/samples/mail/war/Mail.css
@@ -1,11 +1,11 @@
-body, html {
- height: 100%;
+body, table {
+ font-size: small;
}
+
body {
background: #fff;
color: black;
font-family: Helvetica, Arial, sans-serif;
- font-size: small;
margin: 8px;
margin-top: 3px;
}
@@ -24,7 +24,7 @@
.gwt-DialogBox .Caption {
background: url(gradient.gif) repeat-x 0px -1px;
- font-weight: bold;
+ font-weight: normal;
cursor: default;
padding: 5px 10px;
border: 1px solid #666;
@@ -44,26 +44,10 @@
margin: 10px;
}
-.gwt-MenuBar {
- background: #c3d9ff;
- cursor: default;
-}
-
-.gwt-MenuItem {
- font-size: 80%;
- margin: 1px;
- cursor: default;
-}
-
-.gwt-MenuItem-selected {
- background: #e8eef7;
-}
-
.gwt-Tree {
}
.gwt-Tree .gwt-TreeItem {
- font-size: 80%;
padding: 1px 3px 0 3px;
cursor: hand;
cursor: pointer;
@@ -74,142 +58,10 @@
background: #ccc;
}
-.gwt-DecoratedStackPanel {
- width: 15em;
- border-bottom: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .lcaption {
- width: 32px;
- padding: 0 0 4px 5px;
-}
-.gwt-DecoratedStackPanel .rcaption {
- padding: 0 0 4px 5px;
-}
-.gwt-DecoratedStackPanel .gwt-StackPanelContent {
- border: 1px solid #666;
- border-bottom: 0px;
- background: white;
- padding: 2px 2px 10px 5px;
-}
-.gwt-DecoratedStackPanel .gwt-StackPanelItem {
- cursor: pointer;
- cursor: hand;
-}
-.gwt-DecoratedStackPanel .stackItemTopLeft,
-.gwt-DecoratedStackPanel .stackItemTopRight {
- width: 4px;
- height: 4px;
- zoom: 1;
-}
-html>body .gwt-DecoratedStackPanel .stackItemTopLeft {
- background: #c1eec8 url(leftCorner.gif) no-repeat;
- border-left: 1px solid #666;
-}
-html>body .gwt-DecoratedStackPanel .stackItemTopRight {
- background: #c1eec8 url(rightCorner.gif) no-repeat;
- border-right: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemTopLeftInner,
-.gwt-DecoratedStackPanel .stackItemTopRightInner {
- width: 4px;
- height: 4px;
-}
-* html .gwt-DecoratedStackPanel .stackItemTopLeftInner {
- overflow: hidden;
- border-left: 1px solid #666;
- background-color: #d3def6;
- filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='leftCorner.gif',sizingMethod='crop');
-}
-* html .gwt-DecoratedStackPanel .stackItemTopRightInner {
- overflow: hidden;
- border-right: 1px solid #666;
- background-color: #d3def6;
- filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='rightCorner.gif',sizingMethod='crop');
-}
-.gwt-DecoratedStackPanel .stackItemTopCenter {
- background: #ddefde url(gradient.gif) repeat-x 0px 0px;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleLeft {
- background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
- border-left: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleLeftInner,
-.gwt-DecoratedStackPanel .stackItemMiddleRightInner {
- width: 1px;
- height: 1px;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleRight {
- background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
- border-right: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleCenter {
- font-weight: bold;
- font-size: 1.3em;
- background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
-}
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopRight,
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeft {
- border: 0px;
- background-color: white;
-}
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopLeft,
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopRight {
- background-color: white;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeftInner,
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopRightInner {
- border: 0px;
- background-color: white;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeftInner {
- padding-left: 1px;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopLeftInner,
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopRightInner {
- background-color: white;
-}
-
-.mail-TopPanel {
- height: 60px;
-}
-
-.mail-TopPanel table {
- font-size: 80%;
-}
-
-.mail-TopPanel .gwt-Image {
- margin-left: 10px;
- position: absolute;
-}
-
-.mail-TopPanelLinks {
- font-size: 80%;
-}
-
-.mail-AboutText {
- width: 24em;
- font-size: 80%;
- padding: 10px;
- text-align: left;
-}
-
-.mail-Contacts td, .mail-Tasks td {
- padding: 4px 0 0 0;
-}
-
-.mail-Contacts table {
- font-size: 80%;
-}
-
-.mail-Tasks table {
- font-size: 80%;
-}
-
.mail-List {
border-left: 1px solid #666;
border-right: 1px solid #666;
border-bottom: 1px solid #666;
- font-size: 80%;
cursor: pointer;
cursor: hand;
}
@@ -226,11 +78,11 @@
.mail-ListHeader {
background: #c1eec8 url(gradient.gif) repeat-x 0px -1px;
- font-weight: bold;
+ font-weight: normal;
}
.mail-ListHeader .mail-ListNavBar .gwt-HTML {
- font-weight: bold;
+ font-weight: normal;
}
.mail-ListHeader td {
@@ -240,7 +92,6 @@
}
.mail-ListNavBar table {
- font-size: 80%;
}
.mail-ListNavBar td {
@@ -259,76 +110,3 @@
.mail-SelectedRow {
background: #eee;
}
-
-.mail-Toolbar .gwt-Image {
-}
-
-.mail-ToolButton {
- font-size: 80%;
- width: 10em;
-}
-
-.mail-Detail {
- border: 1px solid #666;
- margin-top: 4px;
-}
-
-.mail-DetailHeader {
- background: #eee;
- border-bottom: 1px solid #666;
- padding: 6px 4px;
-}
-
-.mail-DetailHeader td {
- padding: 0;
-}
-
-.mail-DetailInner {
- background-color: white;
- font-size: 80%;
-}
-
-.mail-DetailSubject {
- padding: 2px 10px;
- font-weight: bold;
-}
-
-.mail-DetailSender {
- font-size: 80%;
- padding: 2px 10px;
-}
-
-.mail-DetailRecipient {
- font-size: 80%;
- padding: 2px 10px;
-}
-
-.mail-DetailBody {
- line-height: 150%;
- padding: 20px 40px 20px 10px;
- font-family: 'Times New Roman', Times, serif;
-}
-
-.mail-ContactPopup {
- background: #fff;
- border: 1px solid #666;
- padding: 4px;
-}
-
-.mail-ContactPopupName {
- font-size: 80%;
- font-weight: bold;
-}
-
-.mail-ContactPopupEmail {
- font-size: 80%;
- font-style: italic;
-}
-
-.mail-StackContent {
- height: 100%;
-}
-
-.mail-Contacts {
- border-bottom: none;
-}
diff --git a/samples/mail/war/default_photo.jpg b/samples/mail/war/default_photo.jpg
new file mode 100644
index 0000000..751970d
--- /dev/null
+++ b/samples/mail/war/default_photo.jpg
Binary files differ
diff --git a/samples/mail/war/logo.png b/samples/mail/war/logo.png
new file mode 100644
index 0000000..a3123b2
--- /dev/null
+++ b/samples/mail/war/logo.png
Binary files differ
diff --git a/user/build.xml b/user/build.xml
index c0a3374..a42192c 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -106,8 +106,9 @@
<target name="test.remoteweb" description="Run a remoteweb test at the given host and path" if="gwt.remote.browsers">
<echo message="Performing remote browser testing at ${gwt.remote.browsers}" />
+ <property name="test.remoteweb.args" value="${test.args}" />
<fileset id="test.remoteweb.tests" dir="${javac.junit.out}" includes="${gwt.junit.testcase.web.includes}" excludes="${gwt.junit.testcase.web.excludes}" />
- <gwt.junit test.args="${test.args} -out www -web -runStyle RemoteWeb:${gwt.remote.browsers} -batch module" test.out="${junit.out}/remoteweb" test.cases="test.remoteweb.tests" >
+ <gwt.junit test.args="${test.args} -out www -web -runStyle RemoteWeb:${gwt.remote.browsers} -batch module -precompile parallel" test.out="${junit.out}/remoteweb" test.cases="test.remoteweb.tests" >
<extraclasspaths>
<path refid="test.extraclasspath" />
</extraclasspaths>
@@ -120,8 +121,9 @@
<target name="test.selenium" description="Run a remote test using Selenium RC test at the given host and path" if="gwt.selenium.hosts">
<echo message="Performing remote browser testing using Selenium RC at ${gwt.selenium.hosts}" />
+ <property name="test.selenium.args" value="${test.args}" />
<fileset id="test.selenium.tests" dir="${javac.junit.out}" includes="${gwt.junit.testcase.web.includes}" excludes="${gwt.junit.testcase.web.excludes}" />
- <gwt.junit test.args="${test.args} -out www -web -runStyle Selenium:${gwt.selenium.hosts} -batch module" test.out="${junit.out}/selenium" test.cases="test.selenium.tests" >
+ <gwt.junit test.args="${test.args} -out www -web -runStyle Selenium:${gwt.selenium.hosts} -batch module -precompile parallel" test.out="${junit.out}/selenium" test.cases="test.selenium.tests" >
<extraclasspaths>
<path refid="test.extraclasspath" />
</extraclasspaths>
diff --git a/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
index 31fa1ad..6474fb1 100644
--- a/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
+++ b/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
@@ -20,7 +20,6 @@
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
public class DockLayoutPanelExample implements EntryPoint {
@@ -28,11 +27,11 @@
// Attach five widgets to a DockLayoutPanel, one in each direction. Lay
// them out in 'em' units.
DockLayoutPanel p = new DockLayoutPanel(Unit.EM);
- p.add(new HTML("north"), Direction.NORTH, 2);
- p.add(new HTML("south"), Direction.SOUTH, 2);
- p.add(new HTML("east"), Direction.EAST, 2);
- p.add(new HTML("west"), Direction.WEST, 2);
- p.add(new HTML("center"), Direction.CENTER, 2);
+ p.addNorth(new HTML("north"), 2);
+ p.addSouth(new HTML("south"), 2);
+ p.addEast(new HTML("east"), 2);
+ p.addWest(new HTML("west"), 2);
+ p.add(new HTML("center"));
// Note the explicit call to layout(). This is required for the layout to
// take effect.
diff --git a/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
index e12b1b3..ba71d4e 100644
--- a/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
+++ b/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
@@ -19,16 +19,15 @@
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
-import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
public class SplitLayoutPanelExample implements EntryPoint {
public void onModuleLoad() {
// Create a three-pane layout with splitters.
SplitLayoutPanel p = new SplitLayoutPanel();
- p.add(new HTML("navigation"), Direction.WEST, 128);
- p.add(new HTML("list"), Direction.NORTH, 384);
- p.add(new HTML("details"), Direction.CENTER, 0);
+ p.addWest(new HTML("navigation"), 128);
+ p.addNorth(new HTML("list"), 384);
+ p.add(new HTML("details"));
// Note the explicit call to layout(). This is required for the layout to
// take effect.
diff --git a/user/src/com/google/gwt/benchmarks/BenchmarkShell.java b/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
index c916dc6..1607464 100644
--- a/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
+++ b/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
@@ -16,11 +16,9 @@
package com.google.gwt.benchmarks;
import com.google.gwt.benchmarks.client.Benchmark;
-import com.google.gwt.benchmarks.client.impl.BenchmarkResults;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.junit.JUnitShell;
-import com.google.gwt.junit.JUnitShell.Strategy;
-import com.google.gwt.junit.client.impl.JUnitResult;
+import com.google.gwt.junit.client.GWTTestCase;
import junit.framework.TestCase;
import junit.framework.TestResult;
@@ -35,22 +33,6 @@
*/
public class BenchmarkShell {
- private static class BenchmarkStrategy implements Strategy {
- public String getModuleInherit() {
- return "com.google.gwt.benchmarks.Benchmarks";
- }
-
- public String getSyntheticModuleExtension() {
- return "Benchmarks";
- }
-
- public void processResult(TestCase testCase, JUnitResult result) {
- if (result instanceof BenchmarkResults) {
- report.addBenchmarkResults(testCase, (BenchmarkResults) result);
- }
- }
- }
-
/**
* Executes shutdown logic for JUnitShell
*
@@ -94,14 +76,26 @@
return report;
}
+ /**
+ * @deprecated use {@link #runTest(GWTTestCase, TestResult)} instead
+ */
+ @Deprecated
public static void runTest(String moduleName, TestCase testCase,
TestResult testResult) throws UnableToCompleteException {
if (!shutdownHookSet) {
shutdownHookSet = true;
Runtime.getRuntime().addShutdownHook(new Thread(new Shutdown()));
}
- JUnitShell.runTest(moduleName, testCase, testResult,
- new BenchmarkStrategy());
+ JUnitShell.runTest(moduleName, testCase, testResult);
+ }
+
+ public static void runTest(GWTTestCase testCase,
+ TestResult testResult) throws UnableToCompleteException {
+ if (!shutdownHookSet) {
+ shutdownHookSet = true;
+ Runtime.getRuntime().addShutdownHook(new Thread(new Shutdown()));
+ }
+ JUnitShell.runTest(testCase, testResult);
}
private BenchmarkShell() {
diff --git a/user/src/com/google/gwt/benchmarks/client/Benchmark.java b/user/src/com/google/gwt/benchmarks/client/Benchmark.java
index 43d7638..1532279 100644
--- a/user/src/com/google/gwt/benchmarks/client/Benchmark.java
+++ b/user/src/com/google/gwt/benchmarks/client/Benchmark.java
@@ -16,7 +16,12 @@
package com.google.gwt.benchmarks.client;
import com.google.gwt.benchmarks.BenchmarkShell;
+import com.google.gwt.benchmarks.client.impl.BenchmarkResults;
+import com.google.gwt.junit.JUnitShell.Strategy;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.impl.JUnitResult;
+
+import junit.framework.TestCase;
/**
* A type of {@link com.google.gwt.junit.client.GWTTestCase} which specifically
@@ -100,17 +105,48 @@
public static final String REPORT_PATH = "com.google.gwt.junit.reportPath";
/**
+ * The {@link Strategy} used for benchmarking.
+ */
+ private static Strategy BENCHMARK_STRATEGY = new Strategy() {
+ public String getModuleInherit() {
+ return "com.google.gwt.benchmarks.Benchmarks";
+ }
+
+ public String getSyntheticModuleExtension() {
+ return "Benchmarks";
+ }
+
+ public void processResult(TestCase testCase, JUnitResult result) {
+ if (result instanceof BenchmarkResults) {
+ BenchmarkShell.getReport().addBenchmarkResults(testCase,
+ (BenchmarkResults) result);
+ }
+ }
+ };
+
+ /**
+ * Get the {@link Strategy} to use when compiling and running this test.
+ *
+ * @return the test {@link Strategy}
+ */
+ @Override
+ public Strategy getStrategy() {
+ return BENCHMARK_STRATEGY;
+ }
+
+ /**
* Runs the test via the {@link com.google.gwt.benchmarks.BenchmarkShell}
* environment. Do not override or call this method.
*/
@Override
protected final void runTest() throws Throwable {
- BenchmarkShell.runTest(getModuleName(), this, testResult);
+ BenchmarkShell.runTest(this, testResult);
}
/**
* Benchmarks do not support asynchronous mode.
*/
+ @Override
protected final boolean supportsAsync() {
return false;
}
diff --git a/user/src/com/google/gwt/event/logical/shared/HasInitializeHandlers.java b/user/src/com/google/gwt/event/logical/shared/HasInitializeHandlers.java
new file mode 100644
index 0000000..1eba044
--- /dev/null
+++ b/user/src/com/google/gwt/event/logical/shared/HasInitializeHandlers.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.event.logical.shared;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+
+/**
+ * A widget that implements this interface is a public source of
+ * {@link InitializeEvent} events.
+ *
+ */
+public interface HasInitializeHandlers extends HasHandlers {
+ /**
+ * Adds a {@link InitializeEvent} handler.
+ *
+ * @param handler the handler
+ * @return the registration for the event
+ */
+ HandlerRegistration addInitializeHandler(InitializeHandler handler);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/event/logical/shared/InitializeEvent.java b/user/src/com/google/gwt/event/logical/shared/InitializeEvent.java
new file mode 100644
index 0000000..fbb46df
--- /dev/null
+++ b/user/src/com/google/gwt/event/logical/shared/InitializeEvent.java
@@ -0,0 +1,74 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.event.logical.shared;
+
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HasHandlers;
+
+/**
+ * Fired when the event source is initialized.
+ */
+public class InitializeEvent extends GwtEvent<InitializeHandler> {
+
+ /**
+ * The event type.
+ */
+ private static Type<InitializeHandler> TYPE;
+
+ /**
+ * Fires a initialize event on all registered handlers in the handler source.
+ *
+ * @param <S> The handler source
+ * @param source the source of the handlers
+ */
+ public static <S extends HasInitializeHandlers & HasHandlers> void fire(
+ S source) {
+ if (TYPE != null) {
+ InitializeEvent event = new InitializeEvent();
+ source.fireEvent(event);
+ }
+ }
+
+ /**
+ * Ensures the existence of the handler hook and then returns it.
+ *
+ * @return returns a handler hook
+ */
+ public static Type<InitializeHandler> getType() {
+ if (TYPE == null) {
+ TYPE = new Type<InitializeHandler>();
+ }
+ return TYPE;
+ }
+
+ /**
+ * Construct a new {@link InitializeEvent}.
+ *
+ */
+ protected InitializeEvent() {
+ }
+
+ @Override
+ public final Type<InitializeHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(InitializeHandler handler) {
+ handler.onInitialize(this);
+ }
+}
diff --git a/user/src/com/google/gwt/event/logical/shared/InitializeHandler.java b/user/src/com/google/gwt/event/logical/shared/InitializeHandler.java
new file mode 100644
index 0000000..705419c
--- /dev/null
+++ b/user/src/com/google/gwt/event/logical/shared/InitializeHandler.java
@@ -0,0 +1,31 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.event.logical.shared;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler for {@link InitializeEvent} events.
+ */
+public interface InitializeHandler extends EventHandler {
+ /**
+ * Fired when the widget is initialized.
+ *
+ * @param event the event
+ */
+ void onInitialize(InitializeEvent event);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/BatchingStrategy.java b/user/src/com/google/gwt/junit/BatchingStrategy.java
index c8b102d..1932854 100644
--- a/user/src/com/google/gwt/junit/BatchingStrategy.java
+++ b/user/src/com/google/gwt/junit/BatchingStrategy.java
@@ -18,50 +18,89 @@
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/**
- * An interface that specifies how tests should be batched.
+ * An interface that specifies how tests should be batched. A single batch
+ * should never include tests from more than one module, or the browser will
+ * load the new module and lose results from existing tests.
*/
-public interface BatchingStrategy {
+public abstract class BatchingStrategy {
/**
- * Returns the list of tests that should be executed along with this test.
+ * Returns an ordered list of all tests blocks that should be executed for the
+ * specified module. Each test block is an array of {@link TestInfo}.
+ *
+ * @param syntheticModuleName the name of the synthetic module
+ * @return an ordered list of test blocks to run
*/
- TestInfo[] getTestBlock(TestInfo currentTest);
+ public abstract List<TestInfo[]> getTestBlocks(String syntheticModuleName);
}
/**
*
* Strategy that does not batch tests.
*/
-class NoBatchingStrategy implements BatchingStrategy {
- public TestInfo[] getTestBlock(TestInfo currentTest) {
- return new TestInfo[] {currentTest};
+class NoBatchingStrategy extends BatchingStrategy {
+ @Override
+ public List<TestInfo[]> getTestBlocks(String syntheticModuleName) {
+ Set<TestInfo> allTestsInModule = GWTTestCase.getTestsForModule(
+ syntheticModuleName).getTests();
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ for (TestInfo testInfo : allTestsInModule) {
+ testBlocks.add(new TestInfo[] {testInfo});
+ }
+ return testBlocks;
+ }
+}
+
+/**
+ * Strategy that batches all tests belonging to one class.
+ */
+class ClassBatchingStrategy extends BatchingStrategy {
+ @Override
+ public List<TestInfo[]> getTestBlocks(String syntheticModuleName) {
+ Set<TestInfo> allTestsInModule = GWTTestCase.getTestsForModule(
+ syntheticModuleName).getTests();
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ String lastTestClass = null;
+ List<TestInfo> lastTestBlock = null;
+ for (TestInfo testInfo : allTestsInModule) {
+ String testClass = testInfo.getTestClass();
+ if (!testClass.equals(lastTestClass)) {
+ // Add the last test block to the collection.
+ if (lastTestBlock != null) {
+ testBlocks.add(lastTestBlock.toArray(new TestInfo[lastTestBlock.size()]));
+ }
+
+ // Start a new test block.
+ lastTestClass = testClass;
+ lastTestBlock = new ArrayList<TestInfo>();
+ }
+ lastTestBlock.add(testInfo);
+ }
+
+ // Add the last test block.
+ if (lastTestBlock != null) {
+ testBlocks.add(lastTestBlock.toArray(new TestInfo[lastTestBlock.size()]));
+ }
+ return testBlocks;
}
}
/**
* Strategy that batches all tests belonging to one module.
*/
-class ModuleBatchingStrategy implements BatchingStrategy {
-
- /**
- * Returns the list of all tests belonging to the module of
- * <code>currentTest</code>.
- */
- public TestInfo[] getTestBlock(TestInfo currentTest) {
- String moduleName = currentTest.getTestModule();
- if (moduleName.endsWith(".JUnit")) {
- moduleName = moduleName.substring(0, moduleName.length()
- - ".JUnit".length());
- }
- Set<TestInfo> allTestsInModule = GWTTestCase.ALL_GWT_TESTS.get(moduleName);
- if (allTestsInModule != null) {
- assert allTestsInModule.size() > 0;
- return allTestsInModule.toArray(new TestInfo[allTestsInModule.size()]);
- }
- // No data, default to just this test.
- return new TestInfo[] {currentTest};
+class ModuleBatchingStrategy extends BatchingStrategy {
+ @Override
+ public List<TestInfo[]> getTestBlocks(String syntheticModuleName) {
+ Set<TestInfo> allTestsInModule = GWTTestCase.getTestsForModule(
+ syntheticModuleName).getTests();
+ TestInfo[] testBlock = allTestsInModule.toArray(new TestInfo[allTestsInModule.size()]);
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ testBlocks.add(testBlock);
+ return testBlocks;
}
}
diff --git a/user/src/com/google/gwt/junit/CompileStrategy.java b/user/src/com/google/gwt/junit/CompileStrategy.java
new file mode 100644
index 0000000..c170c43
--- /dev/null
+++ b/user/src/com/google/gwt/junit/CompileStrategy.java
@@ -0,0 +1,279 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.junit.JUnitShell.Strategy;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
+import com.google.gwt.junit.client.impl.GWTRunner;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An interface that specifies how modules should be compiled.
+ */
+public abstract class CompileStrategy {
+
+ /**
+ * The list of modules that have already been compiled. We use this to avoid
+ * adding test batches that have already been added.
+ */
+ private Set<String> compiledModuleNames = new HashSet<String>();
+
+ private final JUnitShell junitShell;
+
+ /**
+ * Construct a CompileStrategy.
+ *
+ * @param junitShell
+ */
+ public CompileStrategy(JUnitShell junitShell) {
+ this.junitShell = junitShell;
+ }
+
+ /**
+ * Let the compile strategy compile another module. This is called while
+ * {@link JUnitShell} is waiting for the current test to complete.
+ *
+ * @throws UnableToCompleteException if the compilation fails
+ */
+ public void maybeCompileAhead() throws UnableToCompleteException {
+ }
+
+ /**
+ * Compile a single module using a synthetic module that adds JUnit support.
+ *
+ * @param moduleName the module name
+ * @param syntheticModuleName the synthetic module name
+ * @param strategy the strategy
+ * @param runStyle the run style
+ * @param batchingStrategy the batching strategy
+ * @param treeLogger the logger
+ * @return the {@link ModuleDef} describing the synthetic module
+ * @throws UnableToCompleteException
+ */
+ public abstract ModuleDef maybeCompileModule(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException;
+
+ /**
+ * Compile a single module using a synthetic module that adds JUnit support.
+ *
+ * @param moduleName the module name
+ * @param syntheticModuleName the synthetic module name
+ * @param strategy the strategy
+ * @param runStyle the run style
+ * @param batchingStrategy the batching strategy
+ * @param treeLogger the logger
+ * @return the {@link ModuleDef} describing the synthetic module
+ */
+ protected ModuleDef maybeCompileModuleImpl(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+
+ /*
+ * Synthesize a synthetic module that derives from the user-specified module
+ * but also includes JUnit support.
+ */
+ ModuleDef moduleDef = ModuleDefLoader.createSyntheticModule(treeLogger,
+ syntheticModuleName, new String[] {
+ moduleName, strategy.getModuleInherit()}, true);
+
+ // Replace any user entry points with our test runner.
+ moduleDef.clearEntryPoints();
+ moduleDef.addEntryPointTypeName(GWTRunner.class.getName());
+
+ // Squirrel away the name of the active module for GWTRunnerGenerator
+ ConfigurationProperty moduleNameProp = moduleDef.getProperties().createConfiguration(
+ "junit.moduleName", false);
+ moduleNameProp.setValue(syntheticModuleName);
+
+ junitShell.maybeCompileForWebMode(syntheticModuleName);
+
+ // Add all test blocks for the module if we haven't seen this module before.
+ if (!compiledModuleNames.contains(syntheticModuleName)) {
+ compiledModuleNames.add(syntheticModuleName);
+ boolean isFinalModule = compiledModuleNames.size() == GWTTestCase.getModuleCount();
+ List<TestInfo[]> testBlocks = batchingStrategy.getTestBlocks(syntheticModuleName);
+ JUnitShell.getMessageQueue().addTestBlocks(testBlocks, isFinalModule);
+ }
+
+ return moduleDef;
+ }
+}
+
+/**
+ * Strategy that compiles modules as tests run. Optimizes total test time.
+ */
+class ParallelCompileStrategy extends PreCompileStrategy {
+
+ /**
+ * The list of all synthetic module names to be compiled.
+ */
+ private List<String> modulesToCompile = new ArrayList<String>();
+
+ /**
+ * The {@link RunStyle} used to compile, which is set on the first compilation
+ * and is the same across all compilations.
+ */
+ private RunStyle runStyle;
+
+ /**
+ * The {@link BatchingStrategy} used to compile, which is set on the first
+ * compilation and is the same across all compilations.
+ */
+ private BatchingStrategy batchingStrategy;
+
+ /**
+ * The {@link TreeLogger} used to compile, which is set on the first
+ * compilation and is the same across all compilations.
+ */
+ private TreeLogger treeLogger;
+
+ public ParallelCompileStrategy(JUnitShell junitShell) {
+ super(junitShell);
+ }
+
+ @Override
+ public void maybeCompileAhead() throws UnableToCompleteException {
+ if (modulesToCompile.size() > 0) {
+ String nextModule = modulesToCompile.remove(0);
+ TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(nextModule);
+ String syntheticModuleName = moduleInfo.getSyntheticModuleName();
+ maybeCompileModuleImpl(moduleInfo.getModuleName(), syntheticModuleName,
+ moduleInfo.getStrategy(), runStyle, batchingStrategy, treeLogger);
+ }
+ }
+
+ @Override
+ public ModuleDef maybeCompileModule(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+
+ // Initialize the map of modules.
+ if (preCompiledModuleDefs == null) {
+ this.runStyle = runStyle;
+ this.batchingStrategy = batchingStrategy;
+ this.treeLogger = treeLogger;
+ preCompiledModuleDefs = new HashMap<String, ModuleDef>();
+ String[] allModuleNames = GWTTestCase.getAllTestModuleNames();
+ for (String curModuleName : allModuleNames) {
+ modulesToCompile.add(curModuleName);
+ }
+ }
+
+ // Compile the requested module if needed.
+ ModuleDef moduleDef = preCompiledModuleDefs.get(syntheticModuleName);
+ if (moduleDef == null) {
+ moduleDef = maybeCompileModuleImpl(moduleName, syntheticModuleName,
+ strategy, runStyle, batchingStrategy, treeLogger);
+ }
+ return moduleDef;
+ }
+
+ @Override
+ protected ModuleDef maybeCompileModuleImpl(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+ modulesToCompile.remove(syntheticModuleName);
+ ModuleDef moduleDef = super.maybeCompileModuleImpl(moduleName,
+ syntheticModuleName, strategy, runStyle, batchingStrategy, treeLogger);
+ preCompiledModuleDefs.put(syntheticModuleName, moduleDef);
+ return moduleDef;
+ }
+}
+
+/**
+ * Strategy that compiles all modules before returning results. Optimizes test
+ * system usage.
+ */
+class PreCompileStrategy extends CompileStrategy {
+ /**
+ * A mapping of synthetic module names to their precompiled synthetic module
+ * defs.
+ */
+ Map<String, ModuleDef> preCompiledModuleDefs;
+
+ public PreCompileStrategy(JUnitShell junitShell) {
+ super(junitShell);
+ }
+
+ @Override
+ public ModuleDef maybeCompileModule(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+ maybePrecompileModules(runStyle, batchingStrategy, treeLogger);
+
+ // Since all test blocks from a module are added to the queue at the
+ // same time, we can safely take the module out of the hash map at
+ // this point.
+ return preCompiledModuleDefs.get(syntheticModuleName);
+ }
+
+ /**
+ * Precompile all modules if needed.
+ */
+ private void maybePrecompileModules(RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+ if (preCompiledModuleDefs == null) {
+ preCompiledModuleDefs = new HashMap<String, ModuleDef>();
+ for (String moduleName : GWTTestCase.getAllTestModuleNames()) {
+ TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName);
+ String syntheticModuleName = moduleInfo.getSyntheticModuleName();
+ ModuleDef moduleDef = maybeCompileModuleImpl(
+ moduleInfo.getModuleName(), syntheticModuleName,
+ moduleInfo.getStrategy(), runStyle, batchingStrategy, treeLogger);
+ preCompiledModuleDefs.put(syntheticModuleName, moduleDef);
+ }
+ }
+ }
+}
+
+/**
+ *
+ * Strategy that compiles only one module at a time. Optimizes memory usage.
+ */
+class SimpleCompileStrategy extends CompileStrategy {
+ public SimpleCompileStrategy(JUnitShell junitShell) {
+ super(junitShell);
+ }
+
+ @Override
+ public ModuleDef maybeCompileModule(String moduleName,
+ String syntheticModuleName, Strategy strategy, RunStyle runStyle,
+ BatchingStrategy batchingStrategy, TreeLogger treeLogger)
+ throws UnableToCompleteException {
+ return maybeCompileModuleImpl(moduleName, syntheticModuleName, strategy,
+ runStyle, batchingStrategy, treeLogger);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/DoNotRunWith.java b/user/src/com/google/gwt/junit/DoNotRunWith.java
index b0e6811..eda8012 100644
--- a/user/src/com/google/gwt/junit/DoNotRunWith.java
+++ b/user/src/com/google/gwt/junit/DoNotRunWith.java
@@ -26,8 +26,6 @@
* the specified platforms. We chose DoNotRunWith instead of RunWith because we
* want each exception to be listed separately here.
*
- * TODO(amitmanjhi): Make this work with batching of test cases.
- *
* <pre>
* @DoNotRunWith({HtmlUnit})
* public class EmulSuite {
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index 4cec301..57ccd4a 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -15,15 +15,17 @@
*/
package com.google.gwt.junit;
+import com.google.gwt.dev.util.collect.HashSet;
import com.google.gwt.junit.client.TimeoutException;
import com.google.gwt.junit.client.impl.JUnitResult;
+import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Set;
/**
* A message queue to pass data between {@link JUnitShell} and
@@ -46,12 +48,8 @@
*/
public static class ClientStatus {
public final String clientId;
- /**
- * Stores the testResults for the current block of tests.
- */
- public Map<TestInfo, JUnitResult> currentTestBlockResults = null;
- public boolean hasRequestedCurrentTest = false;
public boolean isNew = true;
+ public int blockIndex = 0;
public ClientStatus(String clientId) {
this.clientId = clientId;
@@ -64,21 +62,39 @@
private final Map<String, ClientStatus> clientStatuses = new HashMap<String, ClientStatus>();
/**
+ * A set of the GWT user agents (eg. ie6, gecko) that have connected.
+ */
+ private final Set<String> userAgents = new HashSet<String>();
+
+ /**
* The lock used to synchronize access to clientStatuses.
*/
private final Object clientStatusesLock = new Object();
/**
- * The current test to execute.
- */
- private TestInfo[] currentBlock;
-
- /**
* The number of TestCase clients executing in parallel.
*/
private final int numClients;
/**
+ * Maps the TestInfo to the results from each clientId. If JUnitResult is
+ * null, it means that the client requested the test but did not report the
+ * results yet.
+ */
+ private final Map<TestInfo, Map<String, JUnitResult>> testResults = new HashMap<TestInfo, Map<String, JUnitResult>>();
+
+ /**
+ * The list of test blocks to run.
+ */
+ private final List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+
+ /**
+ * Set to true when the last test block has been added. This is used to tell
+ * clients that all tests are complete.
+ */
+ private boolean isLastTestBlockAvailable;
+
+ /**
* Only instantiable within this package.
*
* @param numClients The number of parallel clients being served by this
@@ -93,23 +109,29 @@
/**
* Called by the servlet to query for for the next block to test.
*
+ * @param clientId the ID of the client
+ * @param userAgent the user agent property of the client
+ * @param blockIndex the index of the test block to get
* @param timeout how long to wait for an answer
* @return the next test to run, or <code>null</code> if <code>timeout</code>
* is exceeded or the next test does not match
* <code>testClassName</code>
*/
- public TestInfo[] getNextTestBlock(String clientId, long timeout)
- throws TimeoutException {
+ public TestBlock getTestBlock(String clientId, String userAgent,
+ int blockIndex, long timeout) throws TimeoutException {
synchronized (clientStatusesLock) {
- ClientStatus clientStatus = clientStatuses.get(clientId);
- if (clientStatus == null) {
- clientStatus = new ClientStatus(clientId);
- clientStatuses.put(clientId, clientStatus);
+ userAgents.add(userAgent);
+ ClientStatus clientStatus = ensureClientStatus(clientId);
+ clientStatus.blockIndex = blockIndex;
+
+ // The client has finished all of the tests.
+ if (isLastTestBlockAvailable && blockIndex >= testBlocks.size()) {
+ return null;
}
long startTime = System.currentTimeMillis();
long stopTime = startTime + timeout;
- while (clientStatus.currentTestBlockResults != null) {
+ while (blockIndex >= testBlocks.size()) {
long timeToWait = stopTime - System.currentTimeMillis();
if (timeToWait < 1) {
double elapsed = (System.currentTimeMillis() - startTime) / 1000.0;
@@ -131,57 +153,74 @@
}
}
- // Record that this client has retrieved the current test.
- clientStatus.hasRequestedCurrentTest = true;
- return currentBlock;
+ // Record that this client has retrieved the current tests.
+ TestInfo[] tests = testBlocks.get(blockIndex);
+ for (TestInfo testInfo : tests) {
+ ensureResults(testInfo).put(clientId, null);
+ }
+ return new TestBlock(tests, blockIndex);
}
}
- public void reportFatalLaunch(String clientId, JUnitResult result) {
+ public void reportFatalLaunch(String clientId, String userAgent,
+ JUnitResult result) {
// Fatal launch error, cause this client to fail the whole block.
+ ClientStatus clientStatus = ensureClientStatus(clientId);
Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
- for (TestInfo testInfo : currentBlock) {
+ for (TestInfo testInfo : testBlocks.get(clientStatus.blockIndex)) {
results.put(testInfo, result);
}
- reportResults(clientId, results);
+ reportResults(clientId, userAgent, results);
}
/**
* Called by the servlet to report the results of the last test to run.
*
+ * @param clientId the ID of the client
+ * @param userAgent the user agent property of the client
* @param results the result of running the test block
*/
- public void reportResults(String clientId, Map<TestInfo, JUnitResult> results) {
+ public void reportResults(String clientId, String userAgent,
+ Map<TestInfo, JUnitResult> results) {
synchronized (clientStatusesLock) {
- if (results != null && !resultsMatchCurrentBlock(results)) {
- // A client is reporting results for the wrong test.
- return;
+ if (results == null) {
+ throw new IllegalArgumentException("results cannot be null");
}
- assert (results != null);
- ClientStatus clientStatus = clientStatuses.get(clientId);
- /*
- * Unknown client, but valid testInfo; this can happen if the client's
- * module fails to load.
- */
- if (clientStatus == null) {
- clientStatus = new ClientStatus(clientId);
- clientStatuses.put(clientId, clientStatus);
+ userAgents.add(userAgent);
+ ClientStatus clientStatus = ensureClientStatus(clientId);
+
+ // Cache the test results.
+ for (Map.Entry<TestInfo, JUnitResult> entry : results.entrySet()) {
+ TestInfo testInfo = entry.getKey();
+ ensureResults(testInfo).put(clientId, entry.getValue());
}
- clientStatus.currentTestBlockResults = results;
+
clientStatusesLock.notifyAll();
}
}
/**
- * Gets a human-readable string.
+ * Called by the shell to add test blocks to test.
*
- * @return Fetches a human-readable representation of the current test object
+ * @param isLastBlock true if this is the last test block that will be added
*/
- String getCurrentTestName() {
- if (currentBlock == null) {
- return "(no test)";
+ void addTestBlocks(List<TestInfo[]> newTestBlocks, boolean isLastBlock) {
+ synchronized (clientStatusesLock) {
+ if (isLastTestBlockAvailable) {
+ throw new IllegalArgumentException(
+ "Cannot add test blocks after the last block is added");
+ }
+ for (TestInfo[] testBlock : newTestBlocks) {
+ if (testBlock.length == 0) {
+ throw new IllegalArgumentException("TestBlocks cannot be empty");
+ }
+ }
+ testBlocks.addAll(newTestBlocks);
+ if (isLastBlock) {
+ isLastTestBlockAvailable = true;
+ }
+ clientStatusesLock.notifyAll();
}
- return currentBlock[0].toString();
}
/**
@@ -198,62 +237,68 @@
clientStatus.isNew = false;
}
}
+ clientStatusesLock.notifyAll();
return results.toArray(new String[results.size()]);
}
}
/**
* Returns how many clients have requested the currently-running test.
+ *
+ * @param testInfo the {@link TestInfo} that the clients retrieved
*/
- int getNumClientsRetrievedCurrentTest() {
- int count = 0;
+ int getNumClientsRetrievedTest(TestInfo testInfo) {
synchronized (clientStatusesLock) {
- for (ClientStatus clientStatus : clientStatuses.values()) {
- if (clientStatus.hasRequestedCurrentTest) {
- ++count;
- }
+ int count = 0;
+ Map<String, JUnitResult> results = testResults.get(testInfo);
+ if (results != null) {
+ count = results.size();
}
+ return count;
}
- return count;
+ }
+
+ /**
+ * Returns how many clients have connected.
+ */
+ int getNumConnectedClients() {
+ synchronized (clientStatusesLock) {
+ return clientStatuses.size();
+ }
}
/**
* Fetches the results of a completed test.
*
+ * @param testInfo the {@link TestInfo} to check for results
* @return A map of results from all clients.
*/
- Map<TestInfo, Map<String, JUnitResult>> getResults() {
+ Map<String, JUnitResult> getResults(TestInfo testInfo) {
synchronized (clientStatusesLock) {
- /*
- * All this overly complicated piece of code does is transform mappings
- * keyed by clientId into mappings keyed by TestInfo.
- */
- Map<TestInfo, Map<String, JUnitResult>> result = new HashMap<TestInfo, Map<String, JUnitResult>>();
- for (ClientStatus clientStatus : clientStatuses.values()) {
- for (Entry<TestInfo, JUnitResult> entry : clientStatus.currentTestBlockResults.entrySet()) {
- TestInfo testInfo = entry.getKey();
- JUnitResult clientResultForThisTest = entry.getValue();
- Map<String, JUnitResult> targetMap = result.get(testInfo);
- if (targetMap == null) {
- targetMap = new HashMap<String, JUnitResult>();
- result.put(testInfo, targetMap);
- }
- targetMap.put(clientStatus.clientId, clientResultForThisTest);
- }
- }
- return result;
+ return testResults.get(testInfo);
}
}
/**
+ * Visible for testing.
+ *
+ * @return the test blocks
+ */
+ List<TestInfo[]> getTestBlocks() {
+ return testBlocks;
+ }
+
+ /**
* Returns a pretty printed list of clients that have not retrieved the
* current test. Used for error reporting.
*
+ * @param testInfo the {@link TestInfo} we are waiting for
* @return a string containing the list of clients that have not retrieved the
* current test.
*/
- String getUnretrievedClients() {
+ String getUnretrievedClients(TestInfo testInfo) {
synchronized (clientStatusesLock) {
+ Map<String, JUnitResult> results = testResults.get(testInfo);
StringBuilder buf = new StringBuilder();
int lineCount = 0;
for (ClientStatus clientStatus : clientStatuses.values()) {
@@ -261,7 +306,7 @@
buf.append('\n');
}
- if (!clientStatus.hasRequestedCurrentTest) {
+ if (!results.containsKey(clientStatus.clientId)) {
buf.append(" - NO RESPONSE: ");
} else {
buf.append(" - (ok): ");
@@ -269,7 +314,7 @@
buf.append(clientStatus.clientId);
++lineCount;
}
- int difference = numClients - getNumClientsRetrievedCurrentTest();
+ int difference = numClients - getNumClientsRetrievedTest(testInfo);
if (difference > 0) {
if (lineCount > 0) {
buf.append('\n');
@@ -283,26 +328,39 @@
}
/**
+ * Returns a list of all user agents that have connected.
+ */
+ String[] getUserAgents() {
+ synchronized (clientStatusesLock) {
+ return userAgents.toArray(new String[userAgents.size()]);
+ }
+ }
+
+ /**
* Returns a human-formatted message identifying what clients have connected
* but have not yet reported results for this test. It is used in a timeout
* condition, to identify what we're still waiting on.
*
+ * @param testInfo the {@link TestInfo} that the clients are working on
* @return human readable message
*/
- String getWorkingClients() {
+ String getWorkingClients(TestInfo testInfo) {
synchronized (clientStatusesLock) {
- StringBuilder buf = new StringBuilder();
+ // Print a list of clients that have connected but not returned results.
int itemCount = 0;
- for (ClientStatus clientStatus : clientStatuses.values()) {
- if (clientStatus.hasRequestedCurrentTest
- && clientStatus.currentTestBlockResults == null) {
- if (itemCount > 0) {
- buf.append(", ");
+ StringBuilder buf = new StringBuilder();
+ Map<String, JUnitResult> results = testResults.get(testInfo);
+ if (results != null) {
+ for (Map.Entry<String, JUnitResult> entry : results.entrySet()) {
+ if (entry.getValue() == null) {
+ buf.append(entry.getKey());
+ buf.append("\n");
+ itemCount++;
}
- buf.append(clientStatus.clientId);
- ++itemCount;
}
}
+
+ // Print the number of other clients.
int difference = numClients - itemCount;
if (difference > 0) {
if (itemCount > 0) {
@@ -318,34 +376,22 @@
/**
* Called by the shell to see if the currently-running test has completed.
*
+ * @param testInfo the {@link TestInfo} to check for results
* @return If the test has completed, <code>true</code>, otherwise
* <code>false</code>.
*/
- boolean hasResult() {
+ boolean hasResults(TestInfo testInfo) {
synchronized (clientStatusesLock) {
- if (numClients > clientStatuses.size()) {
+ Map<String, JUnitResult> results = testResults.get(testInfo);
+ if (results == null || results.size() < numClients) {
return false;
}
- for (ClientStatus clientStatus : clientStatuses.values()) {
- if (clientStatus.currentTestBlockResults == null) {
+ for (JUnitResult result : results.values()) {
+ if (result == null) {
return false;
}
}
- }
- return true;
- }
-
- /**
- * Called by the shell to set the next test to run.
- */
- void setNextTestBlock(TestInfo[] testBlock) {
- synchronized (clientStatusesLock) {
- this.currentBlock = testBlock;
- for (ClientStatus clientStatus : clientStatuses.values()) {
- clientStatus.hasRequestedCurrentTest = false;
- clientStatus.currentTestBlockResults = null;
- }
- clientStatusesLock.notifyAll();
+ return true;
}
}
@@ -358,13 +404,34 @@
}
}
- private boolean resultsMatchCurrentBlock(Map<TestInfo, JUnitResult> results) {
- assert results.size() == currentBlock.length;
- for (TestInfo testInfo : currentBlock) {
- if (!results.containsKey(testInfo)) {
- return false;
- }
+ /**
+ * Ensure that a {@link ClientStatus} for the clientId exists.
+ *
+ * @param clientId the id of the client
+ * @return the {@link ClientStatus} for the client
+ */
+ private ClientStatus ensureClientStatus(String clientId) {
+ ClientStatus clientStatus = clientStatuses.get(clientId);
+ if (clientStatus == null) {
+ clientStatus = new ClientStatus(clientId);
+ clientStatuses.put(clientId, clientStatus);
}
- return true;
+ return clientStatus;
+ }
+
+ /**
+ * Get the map of test results from all clients for a given {@link TestInfo},
+ * creating it if necessary.
+ *
+ * @param testInfo the {@link TestInfo}
+ * @return the map of all results
+ */
+ private Map<String, JUnitResult> ensureResults(TestInfo testInfo) {
+ Map<String, JUnitResult> results = testResults.get(testInfo);
+ if (results == null) {
+ results = new HashMap<String, JUnitResult>();
+ testResults.put(testInfo, results);
+ }
+ return results;
}
}
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index ad10b73..080ba4a 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -22,7 +22,6 @@
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.GWTShell;
import com.google.gwt.dev.cfg.BindingProperty;
-import com.google.gwt.dev.cfg.ConfigurationProperty;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.cfg.Properties;
@@ -31,8 +30,8 @@
import com.google.gwt.dev.shell.CheckForUpdates;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.junit.client.TimeoutException;
-import com.google.gwt.junit.client.impl.GWTRunner;
import com.google.gwt.junit.client.impl.JUnitResult;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import com.google.gwt.util.tools.ArgHandlerFlag;
@@ -50,7 +49,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
@@ -245,7 +243,7 @@
@Override
public String[] getTagArgs() {
- return new String[] {"module"};
+ return new String[] {"none|class|module"};
}
@Override
@@ -255,8 +253,14 @@
@Override
public boolean setString(String str) {
- if (str.equals("module")) {
+ if (str.equals("none")) {
+ batchingStrategy = new NoBatchingStrategy();
+ } else if (str.equals("class")) {
+ batchingStrategy = new ClassBatchingStrategy();
+ } else if (str.equals("module")) {
batchingStrategy = new ModuleBatchingStrategy();
+ } else {
+ return false;
}
return true;
}
@@ -279,33 +283,83 @@
return true;
}
});
+
+ registerHandler(new ArgHandlerString() {
+ @Override
+ public String getPurpose() {
+ return "Precompile modules as tests are running (speeds up remote tests but requires more memory)";
+ }
+
+ @Override
+ public String getTag() {
+ return "-precompile";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"simple|all|parallel"};
+ }
+
+ @Override
+ public boolean isUndocumented() {
+ return true;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ if (str.equals("simple")) {
+ compileStrategy = new SimpleCompileStrategy(JUnitShell.this);
+ } else if (str.equals("all")) {
+ compileStrategy = new PreCompileStrategy(JUnitShell.this);
+ } else if (str.equals("parallel")) {
+ compileStrategy = new ParallelCompileStrategy(JUnitShell.this);
+ } else {
+ return false;
+ }
+ return true;
+ }
+ });
+
+ registerHandler(new ArgHandlerString() {
+ @Override
+ public String getPurpose() {
+ return "Specify the user agents to reduce the number of permutations for remote browser tests;"
+ + " e.g. ie6,ie8,safari,gecko,gecko1_8,opera";
+ }
+
+ @Override
+ public String getTag() {
+ return "-userAgents";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"userAgents"};
+ }
+
+ @Override
+ public boolean setString(String str) {
+ remoteUserAgents = str.split(",");
+ for (int i = 0; i < remoteUserAgents.length; i++) {
+ remoteUserAgents[i] = remoteUserAgents[i].trim();
+ }
+ return true;
+ }
+ });
}
}
- private static class JUnitStrategy implements Strategy {
- public String getModuleInherit() {
- return "com.google.gwt.junit.JUnit";
- }
-
- public String getSyntheticModuleExtension() {
- return "JUnit";
- }
-
- public void processResult(TestCase testCase, JUnitResult result) {
- }
- }
-
- /**
- * This is a system property that, when set, emulates command line arguments.
- */
- private static final String PROP_GWT_ARGS = "gwt.args";
-
/**
* The amount of time to wait for all clients to have contacted the server and
* begin running the test. "Contacted" does not necessarily mean "the test has
* begun," e.g. for linker errors stopping the test initialization.
*/
- private static final int TEST_BEGIN_TIMEOUT_MILLIS = 60000;
+ static final int TEST_BEGIN_TIMEOUT_MILLIS = 60000;
+
+ /**
+ * This is a system property that, when set, emulates command line arguments.
+ */
+ private static final String PROP_GWT_ARGS = "gwt.args";
/**
* The amount of time to wait for all clients to complete a single test
@@ -336,20 +390,64 @@
}
/**
- * Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
- * creates the singleton {@link JUnitShell} and invokes its
- * {@link #runTestImpl(String, TestCase, TestResult, Strategy)}.
+ * Get the list of remote user agents to compile. This method returns null
+ * until all clients have connected.
+ *
+ * @return the list of remote user agents
*/
- public static void runTest(String moduleName, TestCase testCase,
- TestResult testResult) throws UnableToCompleteException {
- getUnitTestShell().runTestImpl(moduleName, testCase, testResult,
- new JUnitStrategy());
+ public static String[] getRemoteUserAgents() {
+ return getUnitTestShell().remoteUserAgents;
}
+ /**
+ * Checks if a testCase should not be executed. Currently, a test is either
+ * executed on all clients (mentioned in this test) or on no clients.
+ *
+ * @param testCase current testCase.
+ * @return true iff the test should not be executed on any of the specified
+ * clients.
+ */
+ public static boolean mustNotExecuteTest(TestCase testCase) {
+ return getUnitTestShell().mustNotExecuteTest(getBannedPlatforms(testCase));
+ }
+
+ /**
+ * Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
+ * creates the singleton {@link JUnitShell} and invokes its
+ * {@link #runTestImpl(GWTTestCase, TestResult)}.
+ */
+ public static void runTest(GWTTestCase testCase, TestResult testResult)
+ throws UnableToCompleteException {
+ getUnitTestShell().runTestImpl(testCase, testResult);
+ }
+
+ /**
+ * Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
+ * creates the singleton {@link JUnitShell} and invokes its
+ * {@link #runTestImpl(GWTTestCase, TestResult)}.
+ *
+ * @deprecated use {@link #runTest(GWTTestCase, TestResult)} instead
+ */
+ @Deprecated
+ public static void runTest(String moduleName, TestCase testCase,
+ TestResult testResult) throws UnableToCompleteException {
+ runTest(moduleName, testCase, testResult,
+ ((GWTTestCase) testCase).getStrategy());
+ }
+
+ /**
+ * @deprecated use {@link #runTest(GWTTestCase, TestResult)} instead
+ */
+ @Deprecated
public static void runTest(String moduleName, TestCase testCase,
TestResult testResult, Strategy strategy)
throws UnableToCompleteException {
- getUnitTestShell().runTestImpl(moduleName, testCase, testResult, strategy);
+ GWTTestCase gwtTestCase = (GWTTestCase) testCase;
+ assert moduleName != null : "moduleName cannot be null";
+ assert strategy != null : "strategy cannot be null";
+ assert moduleName.equals(gwtTestCase.getModuleName()) : "moduleName does not match GWTTestCase#getModuleName()";
+ assert strategy.equals(gwtTestCase.getStrategy()) : "strategy does not match GWTTestCase#getStrategy()";
+ runTest(gwtTestCase, testResult);
}
/**
@@ -390,6 +488,31 @@
}
/**
+ * returns the set of banned {@code Platform} for a test method.
+ */
+ private static Set<Platform> getBannedPlatforms(TestCase testCase) {
+ Class<?> testClass = testCase.getClass();
+ Set<Platform> bannedSet = EnumSet.noneOf(Platform.class);
+ if (testClass.isAnnotationPresent(DoNotRunWith.class)) {
+ bannedSet.addAll(Arrays.asList(testClass.getAnnotation(DoNotRunWith.class).value()));
+ }
+ try {
+ Method testMethod = testClass.getMethod(testCase.getName());
+ if (testMethod.isAnnotationPresent(DoNotRunWith.class)) {
+ bannedSet.addAll(Arrays.asList(testMethod.getAnnotation(
+ DoNotRunWith.class).value()));
+ }
+ } catch (SecurityException e) {
+ // should not happen
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // should not happen
+ e.printStackTrace();
+ }
+ return bannedSet;
+ }
+
+ /**
* Retrieves the JUnitShell. This should only be invoked during TestRunner
* execution of JUnit tests.
*/
@@ -423,6 +546,12 @@
private BatchingStrategy batchingStrategy = new NoBatchingStrategy();
/**
+ * Determines how modules are compiled.
+ */
+ private CompileStrategy compileStrategy = new SimpleCompileStrategy(
+ JUnitShell.this);
+
+ /**
* When headless, all logging goes to the console.
*/
private PrintWriterTreeLogger consoleLogger;
@@ -433,6 +562,16 @@
private ModuleDef currentModule;
/**
+ * The name of the current test case being run.
+ */
+ private TestInfo currentTestInfo;
+
+ /**
+ * True if we are running the test in hosted mode.
+ */
+ private boolean developmentMode = true;
+
+ /**
* If true, no launches have yet been successful.
*/
private boolean firstLaunch = true;
@@ -465,14 +604,22 @@
private int numClients = 1;
/**
+ * An exception that should by fired the next time runTestImpl runs.
+ */
+ private UnableToCompleteException pendingException;
+
+ /**
+ * The remote user agents that have connected. Populated after all user agents
+ * have connected so we can limit permutations for remote tests.
+ */
+ private String[] remoteUserAgents;
+
+ /**
* What type of test we're running; Local hosted, local web, or remote web.
*/
private RunStyle runStyle = null;
- /**
- * True if we are running the test in hosted mode
- */
- private boolean developmentMode = true;
+ private boolean shouldAutoGenerateResources = true;
/**
* The time the test actually began.
@@ -493,10 +640,6 @@
*/
private long testMethodTimeout;
- private Map<TestInfo, Map<String, JUnitResult>> cachedResults = new HashMap<TestInfo, Map<String, JUnitResult>>();
-
- private boolean shouldAutoGenerateResources = true;
-
/**
* Enforce the singleton pattern. The call to {@link GWTShell}'s ctor forces
* server mode and disables processing extra arguments as URLs to be shown.
@@ -512,7 +655,9 @@
String url = "http://" + localhost + ":" + getPort() + "/"
+ moduleName + "/junit.html";
if (developmentMode) {
+ // CHECKSTYLE_OFF
url += "?gwt.hosted=" + localhost + ":" + codeServerPort;
+ // CHECKSTYLE_ON
}
return url;
} catch (UnknownHostException e) {
@@ -568,7 +713,7 @@
*/
@Override
protected boolean notDone() {
- int activeClients = messageQueue.getNumClientsRetrievedCurrentTest();
+ int activeClients = messageQueue.getNumClientsRetrievedTest(currentTestInfo);
if (firstLaunch && runStyle instanceof RunStyleManual) {
String[] newClients = messageQueue.getNewClients();
int printIndex = activeClients - newClients.length + 1;
@@ -576,17 +721,31 @@
System.out.println(printIndex + " - " + newClient);
++printIndex;
}
- if (activeClients == this.numClients) {
- System.out.println("Starting tests");
- } else {
+ if (activeClients != this.numClients) {
// Wait forever for first contact; user-driven.
return true;
}
}
+ // Limit permutations after all clients have connected.
+ if (remoteUserAgents == null
+ && messageQueue.getNumConnectedClients() == numClients) {
+ remoteUserAgents = messageQueue.getUserAgents();
+ String userAgentList = "";
+ for (int i = 0; i < remoteUserAgents.length; i++) {
+ if (i > 0) {
+ userAgentList += ", ";
+ }
+ userAgentList += remoteUserAgents[i];
+ }
+ System.out.println("All clients connected (Limiting future permutations to: "
+ + userAgentList + ")");
+ }
+
long currentTimeMillis = System.currentTimeMillis();
if (activeClients == numClients) {
firstLaunch = false;
+
/*
* It's now safe to release any reference to the last module since all
* clients have transitioned to the current module.
@@ -598,18 +757,18 @@
double elapsed = (currentTimeMillis - testBeginTime) / 1000.0;
throw new TimeoutException(
"The browser did not complete the test method "
- + messageQueue.getCurrentTestName() + " in "
+ + currentTestInfo.toString() + " in "
+ TEST_METHOD_TIMEOUT_MILLIS
- + "ms.\n We have no results from: "
- + messageQueue.getWorkingClients() + "\n Actual time elapsed: "
- + elapsed + " seconds.\n");
+ + "ms.\n We have no results from:\n"
+ + messageQueue.getWorkingClients(currentTestInfo)
+ + "Actual time elapsed: " + elapsed + " seconds.\n");
}
} else if (testBeginTimeout < currentTimeMillis) {
double elapsed = (currentTimeMillis - testBeginTime) / 1000.0;
throw new TimeoutException(
"The browser did not contact the server within "
+ TEST_BEGIN_TIMEOUT_MILLIS + "ms.\n"
- + messageQueue.getUnretrievedClients()
+ + messageQueue.getUnretrievedClients(currentTestInfo)
+ "\n Actual time elapsed: " + elapsed + " seconds.\n");
}
@@ -617,7 +776,17 @@
throw new TimeoutException("A remote browser died a mysterious death.");
}
- return !messageQueue.hasResult();
+ if (messageQueue.hasResults(currentTestInfo)) {
+ return false;
+ } else if (pendingException == null) {
+ // Instead of waiting around for results, try to compile the next module.
+ try {
+ compileStrategy.maybeCompileAhead();
+ } catch (UnableToCompleteException e) {
+ pendingException = e;
+ }
+ }
+ return true;
}
protected boolean shouldAutoGenerateResources() {
@@ -641,6 +810,13 @@
super.compile(getTopLogger(), module);
}
+ void maybeCompileForWebMode(String moduleName, String... userAgents)
+ throws UnableToCompleteException {
+ if (!developmentMode || !shouldAutoGenerateResources) {
+ compileForWebMode(moduleName, userAgents);
+ }
+ }
+
/**
* Finish processing command line arguments.
*/
@@ -652,31 +828,6 @@
}
}
- /**
- * returns the set of banned {@code Platform} for a test method.
- */
- private Set<Platform> getBannedPlatforms(TestCase testCase) {
- Class<?> testClass = testCase.getClass();
- Set<Platform> bannedSet = EnumSet.noneOf(Platform.class);
- if (testClass.isAnnotationPresent(DoNotRunWith.class)) {
- bannedSet.addAll(Arrays.asList(testClass.getAnnotation(DoNotRunWith.class).value()));
- }
- try {
- Method testMethod = testClass.getMethod(testCase.getName());
- if (testMethod.isAnnotationPresent(DoNotRunWith.class)) {
- bannedSet.addAll(Arrays.asList(testMethod.getAnnotation(
- DoNotRunWith.class).value()));
- }
- } catch (SecurityException e) {
- // should not happen
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- // should not happen
- e.printStackTrace();
- }
- return bannedSet;
- }
-
private boolean mustNotExecuteTest(Set<Platform> bannedPlatforms) {
// TODO (amitmanjhi): Remove this hard-coding. A RunStyle somehow needs to
// specify how it interacts with the platforms.
@@ -684,24 +835,12 @@
&& bannedPlatforms.contains(Platform.Htmlunit);
}
- /**
- * Checks if a testCase should not be executed. Currently, a test is either
- * executed on all clients (mentioned in this test) or on no clients.
- *
- * @param testCase current testCase.
- * @return true iff the test should not be executed on any of the specified
- * clients.
- */
- private boolean mustNotExecuteTest(TestCase testCase) {
- // TODO: collect stats on tests that were not run
- return mustNotExecuteTest(getBannedPlatforms(testCase));
- }
+ private void processTestResult(TestCase testCase, TestResult testResult,
+ Strategy strategy) {
- private void processTestResult(TestInfo testInfo, TestCase testCase,
- TestResult testResult, Strategy strategy) {
-
- Map<String, JUnitResult> results = cachedResults.get(testInfo);
+ Map<String, JUnitResult> results = messageQueue.getResults(currentTestInfo);
assert results != null;
+ assert results.size() == numClients;
boolean parallelTesting = numClients > 1;
@@ -743,8 +882,7 @@
/**
* Runs a particular test case.
*/
- private void runTestImpl(String moduleName, TestCase testCase,
- TestResult testResult, Strategy strategy)
+ private void runTestImpl(GWTTestCase testCase, TestResult testResult)
throws UnableToCompleteException {
if (mustNotExecuteTest(testCase)) {
@@ -755,33 +893,22 @@
throw new UnableToCompleteException();
}
- String syntheticModuleName = moduleName + "."
- + strategy.getSyntheticModuleExtension();
+ String moduleName = testCase.getModuleName();
+ String syntheticModuleName = testCase.getSyntheticModuleName();
+ Strategy strategy = testCase.getStrategy();
boolean sameTest = (currentModule != null)
&& syntheticModuleName.equals(currentModule.getName());
if (sameTest && lastLaunchFailed) {
throw new UnableToCompleteException();
}
+ // Get the module definition for the current test.
if (!sameTest) {
- /*
- * Synthesize a synthetic module that derives from the user-specified
- * module but also includes JUnit support.
- */
- currentModule = ModuleDefLoader.createSyntheticModule(getTopLogger(),
- syntheticModuleName, new String[] {
- moduleName, strategy.getModuleInherit()}, true);
- // Replace any user entry points with our test runner.
- currentModule.clearEntryPoints();
- currentModule.addEntryPointTypeName(GWTRunner.class.getName());
- // Squirrel away the name of the active module for GWTRunnerGenerator
- ConfigurationProperty moduleNameProp = currentModule.getProperties().createConfiguration(
- "junit.moduleName", false);
- moduleNameProp.setValue(moduleName);
- if (!developmentMode || !shouldAutoGenerateResources) {
- compileForWebMode(syntheticModuleName);
- }
+ currentModule = compileStrategy.maybeCompileModule(moduleName,
+ syntheticModuleName, strategy, runStyle, batchingStrategy,
+ getTopLogger());
}
+ assert (currentModule != null);
JUnitFatalLaunchException launchException = checkTestClassInCurrentModule(
getTopLogger(), currentModule, moduleName, testCase);
@@ -790,20 +917,14 @@
return;
}
- TestInfo testInfo = new TestInfo(currentModule.getName(),
+ currentTestInfo = new TestInfo(currentModule.getName(),
testCase.getClass().getName(), testCase.getName());
- if (cachedResults.containsKey(testInfo)) {
+ if (messageQueue.hasResults(currentTestInfo)) {
// Already have a result.
- processTestResult(testInfo, testCase, testResult, strategy);
+ processTestResult(testCase, testResult, strategy);
return;
}
- /*
- * Need to process test. Set up synchronization.
- */
- TestInfo[] testBlock = batchingStrategy.getTestBlock(testInfo);
- messageQueue.setNextTestBlock(testBlock);
-
try {
if (firstLaunch) {
runStyle.launchModule(currentModule.getName());
@@ -824,16 +945,19 @@
while (notDone()) {
messageQueue.waitForResults(1000);
}
+ if (pendingException != null) {
+ UnableToCompleteException e = pendingException;
+ pendingException = null;
+ throw e;
+ }
} catch (TimeoutException e) {
lastLaunchFailed = true;
testResult.addError(testCase, e);
return;
}
- assert (messageQueue.hasResult());
- cachedResults = messageQueue.getResults();
- assert cachedResults.containsKey(testInfo);
- processTestResult(testInfo, testCase, testResult, strategy);
+ assert (messageQueue.hasResults(currentTestInfo));
+ processTestResult(testCase, testResult, testCase.getStrategy());
}
/**
diff --git a/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java b/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
index 9656891..da5ed27 100644
--- a/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
+++ b/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
@@ -45,16 +45,34 @@
}
}
- private ExternalBrowser[] externalBrowsers;
+ /**
+ * Registered as a shutdown hook to make sure that any browsers that were not
+ * finished are killed.
+ */
+ private class ShutdownCb extends Thread {
+ @Override
+ public void run() {
+ for (ExternalBrowser browser : externalBrowsers) {
+ try {
+ browser.getProcess().exitValue();
+ } catch (IllegalThreadStateException e) {
+ // The process is still active. Kill it.
+ browser.getProcess().destroy();
+ }
+ }
+ }
+ }
+
+ private ExternalBrowser[] externalBrowsers;
+
/**
* @param shell the containing shell
- * @param browsers an array of path names pointing to browser executables.
*/
public RunStyleExternalBrowser(JUnitShell shell) {
super(shell);
}
-
+
@Override
public boolean initialize(String args) {
if (args == null || args.length() == 0) {
@@ -115,23 +133,4 @@
}
return false;
}
-
- /**
- * Registered as a shutdown hook to make sure that any browsers that were not
- * finished are killed.
- */
- private class ShutdownCb extends Thread {
-
- @Override
- public void run() {
- for (ExternalBrowser browser : externalBrowsers) {
- try {
- browser.getProcess().exitValue();
- } catch (IllegalThreadStateException e) {
- // The process is still active. Kill it.
- browser.getProcess().destroy();
- }
- }
- }
- }
}
diff --git a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
index 9377c9c..41de9cf 100644
--- a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
@@ -27,6 +27,7 @@
import java.net.SocketTimeoutException;
import java.rmi.Naming;
import java.rmi.server.RMISocketFactory;
+import java.security.Permission;
/**
* Runs in web mode via browsers managed over RMI. This feature is experimental
@@ -168,6 +169,15 @@
throw new JUnitFatalLaunchException("Error initializing RMISocketFactory",
e);
}
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public void checkPermission(Permission perm) {
+ }
+
+ @Override
+ public void checkPermission(Permission perm, Object context) {
+ }
+ });
int numClients = urls.length;
BrowserManager[] browserManagers = new BrowserManager[numClients];
for (int i = 0; i < numClients; ++i) {
diff --git a/user/src/com/google/gwt/junit/RunStyleSelenium.java b/user/src/com/google/gwt/junit/RunStyleSelenium.java
index 2e95beb..4de5477 100644
--- a/user/src/com/google/gwt/junit/RunStyleSelenium.java
+++ b/user/src/com/google/gwt/junit/RunStyleSelenium.java
@@ -95,7 +95,7 @@
String[] targetsIn = args.split(",");
RCSelenium targets[] = new RCSelenium[targetsIn.length];
- Pattern pattern = Pattern.compile("([\\w\\.-]+):([\\d]+)/([\\w\\s\\*]+)");
+ Pattern pattern = Pattern.compile("([\\w\\.-]+):([\\d]+)/([\\w\\s\\*(/\\w+)*]+)");
for (int i = 0; i < targets.length; ++i) {
Matcher matcher = pattern.matcher(targetsIn[i]);
if (!matcher.matches()) {
diff --git a/user/src/com/google/gwt/junit/client/GWTTestCase.java b/user/src/com/google/gwt/junit/client/GWTTestCase.java
index d2fe917..4ad6866 100644
--- a/user/src/com/google/gwt/junit/client/GWTTestCase.java
+++ b/user/src/com/google/gwt/junit/client/GWTTestCase.java
@@ -16,12 +16,14 @@
package com.google.gwt.junit.client;
import com.google.gwt.junit.JUnitShell;
+import com.google.gwt.junit.JUnitShell.Strategy;
+import com.google.gwt.junit.client.impl.JUnitResult;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import junit.framework.TestCase;
import junit.framework.TestResult;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -43,12 +45,115 @@
public abstract class GWTTestCase extends TestCase {
/**
- * Records all live GWTTestCases by module name so we can optimize run they
- * are compiled and run.
+ * Information about a synthetic module used for testing.
*/
- public static final Map<String, Set<TestInfo>> ALL_GWT_TESTS = new HashMap<String, Set<TestInfo>>();
+ public static final class TestModuleInfo {
+ private String moduleName;
+ private String syntheticModuleName;
+ private Strategy strategy;
+
+ /**
+ * The ordered tests in this synthetic module.
+ */
+ private Set<TestInfo> tests = new LinkedHashSet<TestInfo>();
- /*
+ /**
+ * Construct a new {@link TestModuleInfo}.
+ *
+ * @param moduleName the module name
+ * @param syntheticModuleName the synthetic module name
+ * @param strategy the test {@link Strategy}
+ */
+ public TestModuleInfo(String moduleName, String syntheticModuleName,
+ Strategy strategy) {
+ this.moduleName = moduleName;
+ this.syntheticModuleName = syntheticModuleName;
+ this.strategy = strategy;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public Strategy getStrategy() {
+ return strategy;
+ }
+
+ public String getSyntheticModuleName() {
+ return syntheticModuleName;
+ }
+
+ /**
+ * @return the tests that are part of this module
+ */
+ public Set<TestInfo> getTests() {
+ return tests;
+ }
+ }
+
+ /**
+ * Records all live GWTTestCases by synthetic module name so we can optimize
+ * run they are compiled and run. Ordered so that we can precompile the
+ * modules in the order that they will run.
+ */
+ public static final Map<String, TestModuleInfo> ALL_GWT_TESTS = new LinkedHashMap<String, TestModuleInfo>();
+
+ /**
+ * The lock for ALL_GWT_TESTS.
+ */
+ private static final Object ALL_GWT_TESTS_LOCK = new Object();
+
+ /**
+ * The default strategy to use for tests.
+ */
+ private static final Strategy DEFAULT_STRATEGY = new Strategy() {
+ public String getModuleInherit() {
+ return "com.google.gwt.junit.JUnit";
+ }
+
+ public String getSyntheticModuleExtension() {
+ return "JUnit";
+ }
+
+ public void processResult(TestCase testCase, JUnitResult result) {
+ }
+ };
+
+ /**
+ * Get the names of all test modules.
+ *
+ * @return all test module names
+ */
+ public static String[] getAllTestModuleNames() {
+ synchronized (ALL_GWT_TESTS_LOCK) {
+ return ALL_GWT_TESTS.keySet().toArray(new String[ALL_GWT_TESTS.size()]);
+ }
+ }
+
+ /**
+ * Get the number of modules.
+ *
+ * @return the module count.
+ */
+ public static int getModuleCount() {
+ synchronized (ALL_GWT_TESTS_LOCK) {
+ return ALL_GWT_TESTS.size();
+ }
+ }
+
+ /**
+ * Get the set of all {@link TestInfo} for the specified module.
+ *
+ * @param syntheticModuleName the synthetic module name
+ * @return all tests for the module
+ */
+ public static TestModuleInfo getTestsForModule(String syntheticModuleName) {
+ synchronized (ALL_GWT_TESTS_LOCK) {
+ return ALL_GWT_TESTS.get(syntheticModuleName);
+ }
+ }
+
+ /**
* Object that collects the results of this test case execution.
*/
protected TestResult testResult = null;
@@ -136,6 +241,25 @@
public abstract String getModuleName();
/**
+ * Get the {@link Strategy} to use when compiling and running this test.
+ *
+ * @return the test {@link Strategy}
+ */
+ public Strategy getStrategy() {
+ return DEFAULT_STRATEGY;
+ }
+
+ /**
+ * Get the synthetic module name, which includes the synthetic extension
+ * defined by the {@link Strategy}.
+ *
+ * @return the synthetic module name
+ */
+ public final String getSyntheticModuleName() {
+ return getModuleName() + "." + getStrategy().getSyntheticModuleExtension();
+ }
+
+ /**
* Stashes <code>result</code> so that it can be accessed during
* {@link #runTest()}.
*/
@@ -149,16 +273,26 @@
public void setName(String name) {
super.setName(name);
- // Once the name is set, we can add ourselves to the global set.
- String moduleName = getModuleName();
- Set<TestInfo> testsInThisModule = ALL_GWT_TESTS.get(moduleName);
- if (testsInThisModule == null) {
- // Preserve the order.
- testsInThisModule = new LinkedHashSet<TestInfo>();
- ALL_GWT_TESTS.put(moduleName, testsInThisModule);
+ // If we can't run this test, don't add it to the map of all tests to batch.
+ if (JUnitShell.mustNotExecuteTest(this)) {
+ return;
}
- testsInThisModule.add(new TestInfo(moduleName + ".JUnit",
- getClass().getName(), getName()));
+
+ synchronized (ALL_GWT_TESTS_LOCK) {
+ // Once the name is set, we can add ourselves to the global set.
+ String syntheticModuleName = getSyntheticModuleName();
+ TestModuleInfo moduleInfo = ALL_GWT_TESTS.get(syntheticModuleName);
+ if (moduleInfo == null) {
+ // It should be safe to assume that tests with the same synthetic module
+ // name have the same test strategy. If they didn't, they would compile
+ // over each other.
+ moduleInfo = new TestModuleInfo(getModuleName(), syntheticModuleName,
+ getStrategy());
+ ALL_GWT_TESTS.put(syntheticModuleName, moduleInfo);
+ }
+ moduleInfo.getTests().add(
+ new TestInfo(syntheticModuleName, getClass().getName(), getName()));
+ }
}
/**
@@ -254,7 +388,7 @@
String moduleName = getModuleName();
if (moduleName != null) {
- JUnitShell.runTest(moduleName, this, testResult);
+ JUnitShell.runTest(this, testResult);
} else {
// Run as a non-GWT test
super.runTest();
diff --git a/user/src/com/google/gwt/junit/client/impl/JUnitHost.java b/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
index 18bf8cf..2ccaa88 100644
--- a/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
+++ b/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
@@ -30,6 +30,33 @@
/**
* Returned from the server to tell the system what test to run next.
*/
+ public static class TestBlock implements IsSerializable {
+ private TestInfo[] tests;
+ private int index;
+
+ public TestBlock(TestInfo[] tests, int index) {
+ this.tests = tests;
+ this.index = index;
+ }
+
+ /**
+ * Constructor for serialization.
+ */
+ TestBlock() {
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public TestInfo[] getTests() {
+ return tests;
+ }
+ }
+
+ /**
+ * Returned from the server to tell the system what test to run next.
+ */
public static class TestInfo implements IsSerializable {
private String testClass;
private String testMethod;
@@ -82,21 +109,26 @@
}
/**
- * Gets the name of next method to run.
+ * Gets a specific block of tests to run.
*
- * @return the next test to run
+ * @param blockIndex the index of the test block to retrieve
+ * @param userAgent the user agent property of this client
+ * @return the test block
* @throws TimeoutException if the wait for the next method times out.
*/
- TestInfo[] getFirstMethod() throws TimeoutException;
+ TestBlock getTestBlock(int blockIndex, String userAgent) throws TimeoutException;
/**
* Reports results for the last method run and gets the name of next method to
* run.
*
* @param results the results of executing the test
- * @return the next test to run
+ * @param blockIndex the index of the test block to retrieve
+ * @param userAgent the user agent property of this client
+ * @return the next test block
* @throws TimeoutException if the wait for the next method times out.
*/
- TestInfo[] reportResultsAndGetNextMethod(
- HashMap<TestInfo, JUnitResult> results) throws TimeoutException;
+ TestBlock reportResultsAndGetTestBlock(
+ HashMap<TestInfo, JUnitResult> results, int blockIndex, String userAgent)
+ throws TimeoutException;
}
diff --git a/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java b/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
index 7c25d06..88b7592 100644
--- a/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
+++ b/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.junit.client.impl;
+import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -26,21 +27,26 @@
public interface JUnitHostAsync {
/**
- * Gets the name of next method to run.
+ * Gets a specific block of tests to run.
*
+ * @param blockIndex the index of the test block to retrieve
+ * @param userAgent the user agent property of this client
* @param callBack the object that will receive the name of the next method to
* run
*/
- void getFirstMethod(AsyncCallback<TestInfo[]> callBack);
+ void getTestBlock(int blockIndex, String userAgent,
+ AsyncCallback<TestBlock> callBack);
/**
* Reports results for the last method run and gets the name of next method to
* run.
*
- * @param results the results of the tests
+ * @param results the results of executing the test
+ * @param blockIndex the index of the test block to retrieve
+ * @param userAgent the user agent property of this client
* @param callBack the object that will receive the name of the next method to
* run
*/
- void reportResultsAndGetNextMethod(HashMap<TestInfo, JUnitResult> results,
- AsyncCallback<TestInfo[]> callBack);
+ void reportResultsAndGetTestBlock(HashMap<TestInfo, JUnitResult> results,
+ int blockIndex, String userAgent, AsyncCallback<TestBlock> callBack);
}
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
index f9c16e8..9b20780 100644
--- a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
+++ b/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
@@ -20,12 +20,14 @@
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.SelectionProperty;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
import com.google.gwt.junit.client.impl.GWTRunner;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -86,9 +88,20 @@
throw new UnableToCompleteException();
}
+ String userAgent;
+ try {
+ SelectionProperty prop = context.getPropertyOracle().getSelectionProperty(
+ logger, "user.agent");
+ userAgent = prop.getCurrentValue();
+ } catch (BadPropertyValueException e) {
+ logger.log(TreeLogger.ERROR, "Could not resolve user.agent property", e);
+ throw new UnableToCompleteException();
+ }
+
// Get the stub class name, and see if its source file exists.
//
- String generatedClass = requestedClass.getName().replace('.', '_') + "Impl";
+ String generatedClass = requestedClass.getName().replace('.', '_') + "Impl"
+ + userAgent;
String packageName = requestedClass.getPackage().getName();
String qualifiedStubClassName = packageName + "." + generatedClass;
@@ -97,7 +110,8 @@
if (sourceWriter != null) {
// Check the global set of active tests for this module.
- Set<TestInfo> moduleTests = GWTTestCase.ALL_GWT_TESTS.get(moduleName);
+ TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName);
+ Set<TestInfo> moduleTests = (moduleInfo == null) ? null : moduleInfo.getTests();
Set<String> testClasses;
if (moduleTests == null || moduleTests.isEmpty()) {
// Fall back to pulling in all types in the module.
@@ -111,6 +125,7 @@
}
}
writeCreateNewTestCaseMethod(testClasses, sourceWriter);
+ writeGetUserAgentPropertyMethod(userAgent, sourceWriter);
sourceWriter.commit(logger);
}
@@ -195,4 +210,13 @@
sw.outdent();
sw.println("}");
}
+
+ private void writeGetUserAgentPropertyMethod(String userAgent, SourceWriter sw) {
+ sw.println();
+ sw.println("protected final String getUserAgentProperty() {");
+ sw.indent();
+ sw.println("return \"" + userAgent + "\";");
+ sw.outdent();
+ sw.println("}");
+ }
}
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index a249549..a8b60ad 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -50,10 +50,10 @@
/**
* A maximum timeout to wait for the test system to respond with the next
- * test. Practically speaking, the test system should respond nearly instantly
- * if there are further tests to run.
+ * test. The test system should respond nearly instantly if there are further
+ * tests to run, unless the tests have not yet been compiled.
*/
- private static final int TIME_TO_WAIT_FOR_TESTNAME = 30000;
+ private static final int TIME_TO_WAIT_FOR_TESTNAME = 300000;
/**
* Tries to grab the GWTUnitTestShell sHost environment to communicate with
@@ -81,13 +81,15 @@
fld.set(obj, value);
}
- public TestInfo[] getFirstMethod() throws TimeoutException {
- return getHost().getNextTestBlock(getClientId(getThreadLocalRequest()),
- TIME_TO_WAIT_FOR_TESTNAME);
+ public TestBlock getTestBlock(int blockIndex, String userAgent)
+ throws TimeoutException {
+ return getHost().getTestBlock(getClientId(getThreadLocalRequest()),
+ userAgent, blockIndex, TIME_TO_WAIT_FOR_TESTNAME);
}
- public TestInfo[] reportResultsAndGetNextMethod(
- HashMap<TestInfo, JUnitResult> results) throws TimeoutException {
+ public TestBlock reportResultsAndGetTestBlock(
+ HashMap<TestInfo, JUnitResult> results, int testBlock, String userAgent)
+ throws TimeoutException {
for (JUnitResult result : results.values()) {
initResult(getThreadLocalRequest(), result);
ExceptionWrapper ew = result.getExceptionWrapper();
@@ -95,8 +97,9 @@
}
JUnitMessageQueue host = getHost();
String clientId = getClientId(getThreadLocalRequest());
- host.reportResults(clientId, results);
- return host.getNextTestBlock(clientId, TIME_TO_WAIT_FOR_TESTNAME);
+ host.reportResults(clientId, userAgent, results);
+ return host.getTestBlock(clientId, userAgent, testBlock,
+ TIME_TO_WAIT_FOR_TESTNAME);
}
@Override
@@ -108,7 +111,7 @@
JUnitResult result = new JUnitResult();
initResult(request, result);
result.setException(new JUnitFatalLaunchException(requestPayload));
- getHost().reportFatalLaunch(getClientId(request), result);
+ getHost().reportFatalLaunch(getClientId(request), null, result);
} else {
super.service(request, response);
}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
new file mode 100644
index 0000000..c336ee8
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
@@ -0,0 +1,126 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parses {@link DockLayoutPanel} widgets.
+ *
+ * TODO(jgw): The code that explicitly excludes SplitLayoutPanel in a fairly
+ * awkward way could be greatly simplified if we hoisted the "dock-ness" into an
+ * interface implemented by both DockLayoutPanel and SplitLayoutPanel, and moved
+ * most of this code into a parser for that specific interface. This parser
+ * would then be reduced to a simple special case for the ctor param.
+ */
+public class DockLayoutPanelParser implements ElementParser {
+
+ private static final Map<String, String> DOCK_NAMES = new HashMap<String, String>();
+
+ static {
+ DOCK_NAMES.put("north", "addNorth");
+ DOCK_NAMES.put("south", "addSouth");
+ DOCK_NAMES.put("east", "addEast");
+ DOCK_NAMES.put("west", "addWest");
+ DOCK_NAMES.put("center", "add");
+ }
+
+ /**
+ * TODO(jgw): This will be moved into EnumAttributeParser as soon as I get
+ * around to building it.
+ */
+ static String getFullyQualifiedEnumName(Enum<?> e) {
+ Class<?> cls = e.getClass();
+ String clsName = cls.getCanonicalName();
+ if (clsName == null) {
+ // A synthesized enum subtype (e.g., Unit$3) will have no canonical name
+ // (yet will not be marked as synthetic). Its superclass will be the
+ // one we want (e.g., Unit).
+ clsName = cls.getSuperclass().getCanonicalName();
+ }
+ return clsName + "." + e.name();
+ }
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Generate instantiation (requires a 'unit' ctor param).
+ // (Don't generate a ctor for the SplitLayoutPanel; it's implicitly PX).
+ if (type != getSplitLayoutPanelType(writer)) {
+ Unit unit = elem.consumeEnumAttribute("unit", Unit.class);
+ writer.setFieldInitializerAsConstructor(fieldName,
+ writer.getOracle().findType(DockLayoutPanel.class.getName()),
+ getFullyQualifiedEnumName(unit));
+ }
+
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // Make sure the element is one of the fixed set of valid directions.
+ if (!isValidChildElement(elem, child)) {
+ writer.die(
+ "In %s, child must be one of {north, south, east, west, center}",
+ elem);
+ }
+
+ // Consume the single widget element.
+ XMLElement widget = child.consumeSingleChildElement();
+ String childFieldName = writer.parseElementToField(widget);
+
+ if (requiresSize(child)) {
+ double size = child.consumeDoubleAttribute("size");
+ writer.addStatement("%s.%s(%s, %f);", fieldName, addMethodName(child),
+ childFieldName, size);
+ } else {
+ writer.addStatement("%s.%s(%s);", fieldName, addMethodName(child),
+ childFieldName);
+ }
+ }
+
+ // Emit the layout() call.
+ writer.addStatement("%s.layout();", fieldName);
+ }
+
+ private String addMethodName(XMLElement elem) {
+ return DOCK_NAMES.get(elem.getLocalName());
+ }
+
+ private JClassType getSplitLayoutPanelType(UiBinderWriter writer)
+ throws UnableToCompleteException {
+ try {
+ return writer.getOracle().getType(SplitLayoutPanel.class.getName());
+ } catch (NotFoundException e) {
+ throw new RuntimeException("Unexpected exception", e);
+ }
+ }
+
+ private boolean isValidChildElement(XMLElement parent, XMLElement child) {
+ return child.getNamespaceUri().equals(parent.getNamespaceUri())
+ && DOCK_NAMES.containsKey(child.getLocalName());
+ }
+
+ private boolean requiresSize(XMLElement elem) {
+ return !elem.getLocalName().equals("center");
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
new file mode 100644
index 0000000..58feb85
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
+
+/**
+ * Parses {@link StackLayoutPanel} widgets.
+ */
+public class StackLayoutPanelParser implements ElementParser {
+
+ private static final String HEADER_ELEM = "header";
+ private static final String STACK_ELEM = "stack";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // StackLayoutPanel requires a unit ctor.
+ Unit unit = elem.consumeEnumAttribute("unit", Unit.class);
+ writer.setFieldInitializerAsConstructor(fieldName,
+ writer.getOracle().findType(StackLayoutPanel.class.getName()),
+ DockLayoutPanelParser.getFullyQualifiedEnumName(unit));
+
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // Get the stack element.
+ if (!isElementType(elem, child, STACK_ELEM)) {
+ writer.die("In %s, Only <stack> children are allowed.", elem);
+ }
+
+ XMLElement headerElem = null, widgetElem = null;
+ for (XMLElement stackChild : child.consumeChildElements()) {
+ // Get the header.
+ if (isElementType(elem, stackChild, HEADER_ELEM)) {
+ if (headerElem != null) {
+ writer.die("In %s, Only one <header> allowed per <stack>", elem);
+ }
+ headerElem = stackChild;
+ continue;
+ }
+
+ // Get the widget.
+ if (widgetElem != null) {
+ writer.die("In %s, Only one child widget allowed per <stack>", elem);
+ }
+ widgetElem = stackChild;
+ }
+
+ double size = headerElem.consumeDoubleAttribute("size");
+ XMLElement headerWidgetElem = headerElem.consumeSingleChildElement();
+ String headerFieldName = writer.parseElementToField(headerWidgetElem);
+ String childFieldName = writer.parseElementToField(widgetElem);
+
+ writer.addStatement("%s.add(%s, %s, %f);", fieldName, childFieldName,
+ headerFieldName, size);
+ }
+
+ // Emit the layout() call.
+ writer.addStatement("%s.layout();", fieldName);
+ }
+
+ private boolean isElementType(XMLElement parent, XMLElement child, String type) {
+ return child.getNamespaceUri().equals(parent.getNamespaceUri())
+ && type.equals(child.getLocalName());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index 291368f..24052c5 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -19,7 +19,7 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JType;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -33,7 +33,7 @@
+ " @UiConstructor.";
private final String name;
- private final Set<FieldWriter> needs = new HashSet<FieldWriter>();
+ private final Set<FieldWriter> needs = new LinkedHashSet<FieldWriter>();
private String initializer;
private boolean written;
private MortalLogger logger;
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index f26d9ca..fd00756 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -31,6 +31,9 @@
* document.
*/
public class UiBinderParser {
+ private static final String FIELD_ATTRIBUTE = "field";
+ private static final String SOURCE_ATTRIBUTE = "src";
+
// TODO(rjrjr) Make all the ElementParsers receive their dependencies via
// constructor like this one does, and make this an ElementParser. I want
// guice!!!
@@ -103,10 +106,10 @@
* Interprets <ui:with> elements.
*/
private void createResource(XMLElement elem) throws UnableToCompleteException {
- String resourceName = elem.consumeRequiredAttribute("field");
+ String resourceName = elem.consumeRequiredAttribute(FIELD_ATTRIBUTE);
JClassType resourceType = consumeTypeAttribute(elem);
if (elem.getAttributeCount() > 0) {
- writer.die("In %s, should only find attributes \"field\" and \"type\"");
+ writer.die("In %s, should only find attributes \"field\" and \"type\"", elem);
}
FieldWriter fieldWriter = fieldManager.registerField(resourceType,
@@ -117,7 +120,7 @@
if (ownerField != null) {
if (!resourceType.equals(ownerField.getType().getRawType())) {
- writer.die("In %s, type must match %s", ownerField);
+ writer.die("In %s, type must match %s", elem, ownerField);
}
if (ownerField.isProvided()) {
@@ -143,12 +146,12 @@
private void createStyle(XMLElement elem) throws UnableToCompleteException {
String body = elem.consumeInnerText(new NullInterpreter<String>());
- if (body.length() > 0 && elem.hasAttribute("source")) {
+ if (body.length() > 0 && elem.hasAttribute(SOURCE_ATTRIBUTE)) {
writer.die("In %s, cannot use both a source attribute and inline css text.", elem);
}
- String source = elem.consumeAttribute("source");
- String name = elem.consumeAttribute("field", "style");
+ String source = elem.consumeAttribute(SOURCE_ATTRIBUTE);
+ String name = elem.consumeAttribute(FIELD_ATTRIBUTE, "style");
JClassType publicType = consumeCssResourceType(elem);
ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source,
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index edec5f7..739f365 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -341,6 +341,7 @@
// something like that, but in FieldManager.
fieldName = ("f_" + elem.getLocalName() + (++fieldIndex));
}
+ fieldName = normalizeFieldName(fieldName);
fieldManager.registerField(type, fieldName);
return fieldName;
}
@@ -686,7 +687,7 @@
final String templateResourceName = attribute.getName().split(":")[0];
warn("The %1$s mechanism is deprecated. Instead, declare the following "
+ "%2$s:with element as a child of your %2$s:UiBinder element: "
- + "<%2$s:with name='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
+ + "<%2$s:with field='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
gwtPrefix, templateResourceName, bundleClass.getPackage().getName(),
bundleClass.getName());
@@ -874,6 +875,13 @@
}
}
+ private String normalizeFieldName(String fieldName) {
+ // If a field name has a '.' in it, replace it with '$' to make it a legal
+ // identifier. This can happen with the field names associated with nested
+ // classes.
+ return fieldName.replace('.', '$');
+ }
+
/**
* Parse the document element and return the source of the Java class that
* will implement its UiBinder.
@@ -979,6 +987,9 @@
addWidgetParser("CellPanel");
addWidgetParser("CustomButton");
+ addWidgetParser("DockLayoutPanel");
+ addWidgetParser("StackLayoutPanel");
+
addAttributeParser("boolean",
"com.google.gwt.uibinder.parsers.BooleanAttributeParser");
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 0ff9000..a1c8055 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -206,6 +206,54 @@
}
/**
+ * Consumes the given attribute as a double value.
+ *
+ * @param attr the attribute's full name (including prefix)
+ * @return the attribute's value as a double
+ * @throws UnableToCompleteException
+ */
+ public double consumeDoubleAttribute(String attr)
+ throws UnableToCompleteException {
+ try {
+ return Double.parseDouble(consumeAttribute(attr));
+ } catch (NumberFormatException e) {
+ writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+ + "as a double value", attr, this));
+ return 0; // unreachable line for happy compiler
+ }
+ }
+
+ /**
+ * Consumes the given attribute as an enum value.
+ *
+ * @param attr the attribute's full name (including prefix)
+ * @param type the enumerated type of which this attribute must be a member
+ * @return the attribute's value
+ * @throws UnableToCompleteException
+ */
+ public <T extends Enum<T>> T consumeEnumAttribute(String attr, Class<T> type)
+ throws UnableToCompleteException {
+ String strValue = consumeAttribute(attr);
+
+ // Get the enum value. Enum.valueOf() throws IAE if the specified string is
+ // not valid.
+ T value = null;
+ try {
+ // Enum.valueOf() doesn't accept null arguments.
+ if (strValue != null) {
+ value = Enum.valueOf(type, strValue);
+ }
+ } catch (IllegalArgumentException e) {
+ }
+
+ if (value == null) {
+ writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+ + "as a %s enum", attr, this, type.getSimpleName()));
+ }
+ return value;
+ }
+
+ /**
* Consumes all child elements, and returns an HTML interpretation of them.
* Trailing and leading whitespace is trimmed.
* <p>
@@ -287,6 +335,23 @@
}
/**
+ * Consumes the given attribute as an int value.
+ *
+ * @param attr the attribute's full name (including prefix)
+ * @return the attribute's value as an int
+ * @throws UnableToCompleteException
+ */
+ public int consumeIntAttribute(String attr) throws UnableToCompleteException {
+ try {
+ return Integer.parseInt(consumeAttribute(attr));
+ } catch (NumberFormatException e) {
+ writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+ + "as an int value", attr, this));
+ return 0; // unreachable line for happy compiler
+ }
+ }
+
+ /**
* Consumes all attributes, and returns a string representing the entire
* opening tag. E.g., "<div able='baker'>"
*/
@@ -306,7 +371,7 @@
throws UnableToCompleteException {
String value = consumeAttribute(name);
if ("".equals(value)) {
- writer.die("In %s, missing required attribute name\"%s\"", this, name);
+ writer.die("In %s, missing required attribute name \"%s\"", this, name);
}
return value;
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
index c7ee4f0..460c998 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -29,6 +29,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
/**
* Model class with all attributes of the owner class.
@@ -40,7 +41,7 @@
* Map from field name to model.
*/
private final Map<String, OwnerField> uiFields =
- new HashMap<String, OwnerField>();
+ new TreeMap<String, OwnerField>();
/**
* Map from field type to model.
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
index 33ba429..d16bb54 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -69,7 +69,7 @@
<!--
Tests creating a CssResource from an external file.
-->
-<ui:style field='myStyle' source='WidgetBasedUi.css'
+<ui:style field='myStyle' src='WidgetBasedUi.css'
type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
<ui:style field='myOtherStyle' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'>
diff --git a/user/src/com/google/gwt/user/client/Event.java b/user/src/com/google/gwt/user/client/Event.java
index 5cf2251..5228aa6 100644
--- a/user/src/com/google/gwt/user/client/Event.java
+++ b/user/src/com/google/gwt/user/client/Event.java
@@ -72,13 +72,27 @@
*/
private static boolean fire(HandlerManager handlers, NativeEvent nativeEvent) {
if (TYPE != null && handlers != null && handlers.isEventHandled(TYPE)) {
+ // Cache the current values in the singleton in case we are in the
+ // middle of handling another event.
+ boolean lastIsCanceled = singleton.isCanceled;
+ boolean lastIsConsumed = singleton.isConsumed;
+ boolean lastIsFirstHandler = singleton.isFirstHandler;
+ NativeEvent lastNativeEvent = singleton.nativeEvent;
+
// Revive the event
singleton.revive();
singleton.setNativeEvent(nativeEvent);
// Fire the event
handlers.fireEvent(singleton);
- return !(singleton.isCanceled() && !singleton.isConsumed());
+ boolean ret = !(singleton.isCanceled() && !singleton.isConsumed());
+
+ // Restore the state of the singleton.
+ singleton.isCanceled = lastIsCanceled;
+ singleton.isConsumed = lastIsConsumed;
+ singleton.isFirstHandler = lastIsFirstHandler;
+ singleton.nativeEvent = lastNativeEvent;
+ return ret;
}
return true;
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
index 09509e6..af01b54 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
@@ -31,6 +31,23 @@
@SuppressWarnings("unused")
private static JavaScriptObject dispatchDblClickEvent;
+ /**
+ * Let every GWT app on the page preview the current event. If any app cancels
+ * the event, the event will be canceled for all apps.
+ *
+ * @return <code>false</code> to cancel the event
+ */
+ @SuppressWarnings("unused")
+ private static native boolean previewEventImpl() /*-{
+ var isCancelled = false;
+ for (var i = 0; i < $wnd.__gwt_globalEventArray.length; i++) {
+ if (!$wnd.__gwt_globalEventArray[i]()) {
+ isCancelled = true;
+ }
+ }
+ return !isCancelled;
+ }-*/;
+
@Override
public native Element eventGetFromElement(Event evt) /*-{
// Prefer 'relatedTarget' if it's set (see createMouseEvent(), which
@@ -68,6 +85,24 @@
@Override
public native void initEventSystem() /*-{
+ // All GWT apps on the page register themselves with the globelEventArray
+ // so that the first app to handle an event can allow all apps on the page
+ // to preview it. See issue 3892 for more details.
+ //
+ // Apps cannot just mark the event as they visit it for a few reasons.
+ // First, window level event handlers fire last in IE, so the first app to
+ // cancel the event will be the last to see it. Second, window events do
+ // not support arbitrary attributes, and the only writable field is the
+ // returnValue, which has another use. Finally, window events are not
+ // comparable (ex. a=event; b=event; a!=b), so we cannot keep a list of
+ // events that have already been previewed by the current app.
+ if ($wnd.__gwt_globalEventArray == null) {
+ $wnd.__gwt_globalEventArray = new Array();
+ }
+ $wnd.__gwt_globalEventArray[$wnd.__gwt_globalEventArray.length] = function() {
+ return @com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event);
+ }
+
@com.google.gwt.user.client.impl.DOMImplTrident::dispatchEvent = function() {
// IE doesn't define event.currentTarget, so we squirrel it away here. It
// also seems that IE won't allow you to add expandos to the event object,
@@ -76,9 +111,12 @@
var oldEventTarget = @com.google.gwt.dom.client.DOMImplTrident::currentEventTarget;
@com.google.gwt.dom.client.DOMImplTrident::currentEventTarget = this;
+ // The first GWT app on the page to handle the event allows all apps to
+ // preview it before continuing or cancelling, which is consistent with
+ // other browsers.
if ($wnd.event.returnValue == null) {
$wnd.event.returnValue = true;
- if (!@com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event)) {
+ if (!@com.google.gwt.user.client.impl.DOMImplTrident::previewEventImpl()()) {
@com.google.gwt.dom.client.DOMImplTrident::currentEventTarget = oldEventTarget;
return;
}
@@ -109,7 +147,7 @@
} else if ($wnd.event.returnValue == null) {
// Ensure that we preview the event even if we aren't handling it.
$wnd.event.returnValue = true;
- @com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event);
+ @com.google.gwt.user.client.impl.DOMImplTrident::previewEventImpl()();
}
};
diff --git a/user/src/com/google/gwt/user/client/ui/AttachDetachException.java b/user/src/com/google/gwt/user/client/ui/AttachDetachException.java
new file mode 100644
index 0000000..0941285
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/AttachDetachException.java
@@ -0,0 +1,115 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An exception that is thrown when the panel fails to attach or detach its
+ * children.
+ */
+public class AttachDetachException extends RuntimeException {
+
+ /**
+ * The singleton command used to attach widgets.
+ */
+ static final AttachDetachException.Command attachCommand = new AttachDetachException.Command() {
+ public void execute(Widget w) {
+ w.onAttach();
+ }
+ };
+
+ /**
+ * The singleton command used to detach widgets.
+ */
+ static final AttachDetachException.Command detachCommand = new AttachDetachException.Command() {
+ public void execute(Widget w) {
+ w.onDetach();
+ }
+ };
+
+ /**
+ * The command to execute when iterating through child widgets.
+ */
+ public static interface Command {
+ void execute(Widget w);
+ }
+
+ /**
+ * <p>
+ * Iterator through all child widgets, trying to perform the specified
+ * {@link Command} for each. All widgets will be visited even if the Command
+ * throws an exception. If one or more exceptions occur, they will be combined
+ * and thrown as a single {@link AttachDetachException}.
+ * </p>
+ * <p>
+ * Use this method when attaching or detaching a widget with children to
+ * ensure that the logical and physical state of all children match the
+ * logical and physical state of the parent.
+ * </p>
+ *
+ * @param hasWidgets children to iterate
+ * @param c the {@link Command} to try on all children
+ */
+ public static void tryCommand(Iterable<Widget> hasWidgets, Command c) {
+ Set<Throwable> caught = null;
+ for (Widget w : hasWidgets) {
+ try {
+ c.execute(w);
+ } catch (Throwable e) {
+ // Catch all exceptions to prevent some children from being attached
+ // while others are not.
+ if (caught == null) {
+ caught = new HashSet<Throwable>();
+ }
+ caught.add(e);
+ }
+ }
+
+ // Throw the combined exceptions now that all children are attached.
+ if (caught != null) {
+ throw new AttachDetachException(caught);
+ }
+ }
+
+ /**
+ * The causes of the exception.
+ */
+ private Set<Throwable> causes;
+
+ /**
+ * Construct a new {@link AttachDetachException}.
+ *
+ * @param causes the causes of the exception
+ */
+ public AttachDetachException(Set<Throwable> causes) {
+ super(
+ "One or more exceptions caught, see full set in AttachDetachException#getCauses",
+ causes.size() == 0 ? null : causes.toArray(new Throwable[0])[0]);
+ this.causes = causes;
+ }
+
+ /**
+ * Get the set of exceptions that cause {@link Panel#doAttachChildren()} or
+ * {@link Panel#doDetachChildren()} to fail.
+ *
+ * @return the set of causes
+ */
+ public Set<Throwable> getCauses() {
+ return causes;
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/ComplexPanel.java b/user/src/com/google/gwt/user/client/ui/ComplexPanel.java
index 6d87f62..c193e22 100644
--- a/user/src/com/google/gwt/user/client/ui/ComplexPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/ComplexPanel.java
@@ -27,6 +27,11 @@
private WidgetCollection children = new WidgetCollection(this);
+ /**
+ * The command used to orphan children.
+ */
+ private AttachDetachException.Command orphanCommand;
+
public Widget getWidget(int index) {
return getChildren().get(index);
}
@@ -54,14 +59,16 @@
return false;
}
// Orphan.
- orphan(w);
-
- // Physical detach.
- Element elem = w.getElement();
- DOM.removeChild(DOM.getParent(elem), elem);
-
- // Logical detach.
- getChildren().remove(w);
+ try {
+ orphan(w);
+ } finally {
+ // Physical detach.
+ Element elem = w.getElement();
+ DOM.removeChild(DOM.getParent(elem), elem);
+
+ // Logical detach.
+ getChildren().remove(w);
+ }
return true;
}
@@ -199,10 +206,22 @@
}
void doLogicalClear() {
- int size = children.size();
- for (int i = 0; i < size; i++) {
- orphan(children.get(i));
+ // TODO(jgw): When Layout work has landed, deprecate FlowPanel (the only
+ // caller of this method in our code), and deprecate this method with an eye
+ // to making it private down the road.
+
+ // Only use one orphan command per panel to avoid object creation.
+ if (orphanCommand == null) {
+ orphanCommand = new AttachDetachException.Command() {
+ public void execute(Widget w) {
+ orphan(w);
+ }
+ };
}
- children = new WidgetCollection(this);
+ try {
+ AttachDetachException.tryCommand(this, orphanCommand);
+ } finally {
+ children = new WidgetCollection(this);
+ }
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/DialogBox.java b/user/src/com/google/gwt/user/client/ui/DialogBox.java
index fd2172a..3eb76cb 100644
--- a/user/src/com/google/gwt/user/client/ui/DialogBox.java
+++ b/user/src/com/google/gwt/user/client/ui/DialogBox.java
@@ -353,21 +353,25 @@
@Override
protected void doAttachChildren() {
- super.doAttachChildren();
-
- // See comment in doDetachChildren for an explanation of this call
- caption.onAttach();
+ try {
+ super.doAttachChildren();
+ } finally {
+ // See comment in doDetachChildren for an explanation of this call
+ caption.onAttach();
+ }
}
@Override
protected void doDetachChildren() {
- super.doDetachChildren();
-
- // We need to detach the caption specifically because it is not part of the
- // iterator of Widgets that the {@link SimplePanel} super class returns.
- // This is similar to a {@link ComplexPanel}, but we do not want to expose
- // the caption widget, as its just an internal implementation.
- caption.onDetach();
+ try {
+ super.doDetachChildren();
+ } finally {
+ // We need to detach the caption specifically because it is not part of the
+ // iterator of Widgets that the {@link SimplePanel} super class returns.
+ // This is similar to a {@link ComplexPanel}, but we do not want to expose
+ // the caption widget, as its just an internal implementation.
+ caption.onDetach();
+ }
}
/**
diff --git a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
index 58e15d3..f34524b 100644
--- a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
@@ -54,8 +54,8 @@
RequiresResize, ProvidesResize {
/**
- * Used in {@link DockLayoutPanel#add(Widget, Direction, double)} to specify
- * the direction in which a child widget will be added.
+ * Used in {@link DockLayoutPanel#addEast(Widget, double)} et al to
+ * specify the direction in which a child widget will be added.
*/
public enum Direction {
NORTH, EAST, SOUTH, WEST, CENTER, LINE_START, LINE_END
@@ -95,23 +95,70 @@
}
/**
- * Adds a widget to the specified edge of the dock. If the widget is already a
- * child of this panel, this method behaves as though {@link #remove(Widget)}
- * had already been called.
+ * Adds a widget at the center of the dock. No further widgets may be added
+ * after this one.
*
* @param widget the widget to be added
- * @param direction the widget's direction in the dock
- * @param size the child widget's size
- *
- * @throws IllegalArgumentException when adding to the {@link #CENTER} and
- * there is already a different widget there
*/
- public void add(Widget widget, Direction direction, double size) {
- insert(widget, direction, size, null);
+ @Override
+ public void add(Widget widget) {
+ insert(widget, Direction.CENTER, 0, null);
}
/**
+ * Adds a widget to the east edge of the dock.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ */
+ public void addEast(Widget widget, double size) {
+ insert(widget, Direction.EAST, size, null);
+ }
+
+ /**
+ * Adds a widget to the north edge of the dock.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ */
+ public void addNorth(Widget widget, double size) {
+ insert(widget, Direction.NORTH, size, null);
+ }
+
+ /**
+ * Adds a widget to the south edge of the dock.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ */
+ public void addSouth(Widget widget, double size) {
+ insert(widget, Direction.SOUTH, size, null);
+ }
+
+ /**
+ * Adds a widget to the west edge of the dock.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ */
+ public void addWest(Widget widget, double size) {
+ insert(widget, Direction.WEST, size, null);
+ }
+
+ /**
+ * Gets the container element associated with the given child widget.
+ *
+ * <p>
+ * The container element is created by the {@link Layout} class. This should
+ * be used with certain styles, such as
+ * {@link com.google.gwt.dom.client.Style#setZIndex(int)}, that must be
+ * applied to the container, rather than directly to the child widget.
+ * </p>
+ *
* TODO(jgw): Is this really the best way to do this?
+ *
+ * @param widget the widget whose container element is to be retrieved
+ * @return the widget's container element
*/
public Element getContainerElementFor(Widget widget) {
assertIsChild(widget);
@@ -133,46 +180,55 @@
}
/**
- * Adds a widget to the specified edge of the dock. If the widget is already a
- * child of this panel, this method behaves as though {@link #remove(Widget)}
- * had already been called.
+ * Adds a widget to the east edge of the dock, inserting it before an
+ * existing widget.
*
* @param widget the widget to be added
- * @param direction the widget's direction in the dock
+ * @param size the child widget's size
* @param before the widget before which to insert the new child, or
* <code>null</code> to append
- *
- * @throws IllegalArgumentException when adding to the {@link #CENTER} and
- * there is already a different widget there
*/
- public void insert(Widget widget, Direction direction, double size,
- Widget before) {
- assertIsChild(before);
+ public void insertEast(Widget widget, double size, Widget before) {
+ insert(widget, Direction.EAST, size, before);
+ }
- // Validation.
- if (before == null) {
- assert center == null : "No widget may be added after the CENTER widget";
- } else {
- assert direction != Direction.CENTER : "A CENTER widget must always be added last";
- }
+ /**
+ * Adds a widget to the north edge of the dock, inserting it before an
+ * existing widget.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ * @param before the widget before which to insert the new child, or
+ * <code>null</code> to append
+ */
+ public void insertNorth(Widget widget, double size, Widget before) {
+ insert(widget, Direction.NORTH, size, before);
+ }
- // Detach new child.
- widget.removeFromParent();
+ /**
+ * Adds a widget to the south edge of the dock, inserting it before an
+ * existing widget.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ * @param before the widget before which to insert the new child, or
+ * <code>null</code> to append
+ */
+ public void insertSouth(Widget widget, double size, Widget before) {
+ insert(widget, Direction.SOUTH, size, before);
+ }
- // Logical attach.
- getChildren().add(widget);
- if (direction == Direction.CENTER) {
- center = widget;
- }
-
- // Physical attach.
- Layer layer = layout.attachChild(widget.getElement(),
- (before != null) ? before.getElement() : null, widget);
- LayoutData data = new LayoutData(direction, size, layer);
- widget.setLayoutData(data);
-
- // Adopt.
- adopt(widget);
+ /**
+ * Adds a widget to the west edge of the dock, inserting it before an
+ * existing widget.
+ *
+ * @param widget the widget to be added
+ * @param size the child widget's size
+ * @param before the widget before which to insert the new child, or
+ * <code>null</code> to append
+ */
+ public void insertWest(Widget widget, double size, Widget before) {
+ insert(widget, Direction.WEST, size, before);
}
public void layout() {
@@ -284,6 +340,46 @@
return unit;
}
+ /**
+ * Adds a widget to the specified edge of the dock. If the widget is already a
+ * child of this panel, this method behaves as though {@link #remove(Widget)}
+ * had already been called.
+ *
+ * @param widget the widget to be added
+ * @param direction the widget's direction in the dock
+ * @param before the widget before which to insert the new child, or
+ * <code>null</code> to append
+ */
+ protected void insert(Widget widget, Direction direction, double size,
+ Widget before) {
+ assertIsChild(before);
+
+ // Validation.
+ if (before == null) {
+ assert center == null : "No widget may be added after the CENTER widget";
+ } else {
+ assert direction != Direction.CENTER : "A CENTER widget must always be added last";
+ }
+
+ // Detach new child.
+ widget.removeFromParent();
+
+ // Logical attach.
+ getChildren().add(widget);
+ if (direction == Direction.CENTER) {
+ center = widget;
+ }
+
+ // Physical attach.
+ Layer layer = layout.attachChild(widget.getElement(),
+ (before != null) ? before.getElement() : null, widget);
+ LayoutData data = new LayoutData(direction, size, layer);
+ widget.setLayoutData(data);
+
+ // Adopt.
+ adopt(widget);
+ }
+
@Override
protected void onLoad() {
layout.onAttach();
@@ -295,6 +391,6 @@
}
private void assertIsChild(Widget widget) {
- assert (widget == null) || (widget.getParent() == this) : "TODO";
+ assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel";
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/FlowPanel.java b/user/src/com/google/gwt/user/client/ui/FlowPanel.java
index d27c74c..0312b33 100644
--- a/user/src/com/google/gwt/user/client/ui/FlowPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/FlowPanel.java
@@ -47,13 +47,15 @@
@Override
public void clear() {
- doLogicalClear();
-
- // Remove all existing child nodes.
- Node child = getElement().getFirstChild();
- while (child != null) {
- getElement().removeChild(child);
- child = getElement().getFirstChild();
+ try {
+ doLogicalClear();
+ } finally {
+ // Remove all existing child nodes.
+ Node child = getElement().getFirstChild();
+ while (child != null) {
+ getElement().removeChild(child);
+ child = getElement().getFirstChild();
+ }
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/HTMLTable.java b/user/src/com/google/gwt/user/client/ui/HTMLTable.java
index dfb5538..2527b01 100644
--- a/user/src/com/google/gwt/user/client/ui/HTMLTable.java
+++ b/user/src/com/google/gwt/user/client/ui/HTMLTable.java
@@ -948,14 +948,16 @@
}
// Orphan.
- orphan(widget);
-
- // Physical detach.
- Element elem = widget.getElement();
- DOM.removeChild(DOM.getParent(elem), elem);
-
- // Logical detach.
- widgetMap.removeByElement(elem);
+ try {
+ orphan(widget);
+ } finally {
+ // Physical detach.
+ Element elem = widget.getElement();
+ DOM.removeChild(DOM.getParent(elem), elem);
+
+ // Logical detach.
+ widgetMap.removeByElement(elem);
+ }
return true;
}
diff --git a/user/src/com/google/gwt/user/client/ui/Panel.java b/user/src/com/google/gwt/user/client/ui/Panel.java
index 593852d..a50d4bb 100644
--- a/user/src/com/google/gwt/user/client/ui/Panel.java
+++ b/user/src/com/google/gwt/user/client/ui/Panel.java
@@ -159,20 +159,12 @@
@Override
protected void doAttachChildren() {
- // Ensure that all child widgets are attached.
- for (Iterator<Widget> it = iterator(); it.hasNext();) {
- Widget child = it.next();
- child.onAttach();
- }
+ AttachDetachException.tryCommand(this, AttachDetachException.attachCommand);
}
@Override
protected void doDetachChildren() {
- // Ensure that all child widgets are detached.
- for (Iterator<Widget> it = iterator(); it.hasNext();) {
- Widget child = it.next();
- child.onDetach();
- }
+ AttachDetachException.tryCommand(this, AttachDetachException.detachCommand);
}
/**
@@ -196,12 +188,19 @@
}
/**
+ * <p>
* This method must be called as part of the remove method of any Panel. It
* ensures that the Widget's parent is cleared. This method should be called
* after verifying that the child Widget is an existing child of the Panel,
* but before physically removing the child Widget from the DOM. The child
* will now fire its {@link Widget#onDetach()} event if this Panel is
* currently attached.
+ * </p>
+ * <p>
+ * Calls to {@link #orphan(Widget)} should be wrapped in a try/finally block
+ * to ensure that the widget is physically detached even if orphan throws an
+ * exception.
+ * </p>
*
* @param child the widget to be disowned
* @see #add(Widget)
diff --git a/user/src/com/google/gwt/user/client/ui/RichTextArea.java b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
index 92731c5..28f6af0 100644
--- a/user/src/com/google/gwt/user/client/ui/RichTextArea.java
+++ b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
@@ -17,6 +17,10 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.HasAllMouseHandlers;
+import com.google.gwt.event.logical.shared.HasInitializeHandlers;
+import com.google.gwt.event.logical.shared.InitializeEvent;
+import com.google.gwt.event.logical.shared.InitializeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.impl.RichTextAreaImpl;
/**
@@ -24,8 +28,8 @@
*
* Because some browsers do not support rich text editing, and others support
* only a limited subset of functionality, there are two formatter interfaces,
- * accessed via {@link #getBasicFormatter()} and {@link #getExtendedFormatter()}.
- * A browser that does not support rich text editing at all will return
+ * accessed via {@link #getBasicFormatter()} and {@link #getExtendedFormatter()}
+ * . A browser that does not support rich text editing at all will return
* <code>null</code> for both of these, while one that supports only the basic
* functionality will return <code>null</code> for the latter.
*
@@ -33,14 +37,15 @@
* <img class='gallery' src='RichTextArea.png'/>
* </p>
*
- * <h3>CSS Style Rules</h3>
- * <ul class="css">
- * <li>.gwt-RichTextArea { }</li>
- * </ul>
+ * <h3>CSS Style Rules</h3>
+ * <dl>
+ * <dt>.gwt-RichTextArea</dt>
+ * <dd>Applied to the rich text element.</dd>
+ * </dl>
*/
@SuppressWarnings("deprecation")
public class RichTextArea extends FocusWidget implements HasHTML,
- SourcesMouseEvents, HasAllMouseHandlers {
+ SourcesMouseEvents, HasAllMouseHandlers, HasInitializeHandlers {
/**
* <p>
@@ -51,10 +56,10 @@
* </p>
* <p>
* The formatter will format the user selected text in the
- * {@link RichTextArea}. As a result, it will only work reliably if the
+ * {@link RichTextArea}. As a result, it will only work reliably if the
* {@link RichTextArea} is attached, visible to on the page, and has been
- * focused at least once. If you just want to initialize the content of
- * the {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
+ * focused at least once. If you just want to initialize the content of the
+ * {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
* </p>
*
* @deprecated use {@link Formatter} instead
@@ -186,10 +191,10 @@
* </p>
* <p>
* The formatter will format the user selected text in the
- * {@link RichTextArea}. As a result, it will only work reliably if the
+ * {@link RichTextArea}. As a result, it will only work reliably if the
* {@link RichTextArea} is attached, visible to on the page, and has been
- * focused at least once. If you just want to initialize the content of
- * the {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
+ * focused at least once. If you just want to initialize the content of the
+ * {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
* </p>
*
* @deprecated use {@link Formatter} instead
@@ -260,255 +265,46 @@
}
/**
- * <p>
- * This interface is used to access full formatting options, when available.
- * If the implementation supports full formatting, then
- * {@link RichTextArea#getFormatter()} will return an instance of this
- * class.
- * </p>
- * <p>
- * The formatter will format the user selected text in the
- * {@link RichTextArea}. As a result, it will only work reliably if the
- * {@link RichTextArea} is attached, visible to on the page, and has been
- * focused at least once. If you just want to initialize the content of
- * the {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
- * </p>
- */
- public interface Formatter extends ExtendedFormatter {
- /**
- * Creates a link to the supplied URL.
- *
- * @param url the URL to be linked to
- */
- void createLink(String url);
-
- /**
- * Gets the background color.
- *
- * @return the background color
- */
- String getBackColor();
-
- /**
- * Gets the foreground color.
- *
- * @return the foreground color
- */
- String getForeColor();
-
- /**
- * Inserts a horizontal rule.
- */
- void insertHorizontalRule();
-
- /**
- * Inserts generic html.
- *
- * @param html the HTML to insert
- */
- void insertHTML(String html);
-
- /**
- * Inserts an image element.
- *
- * @param url the url of the image to be inserted
- */
- void insertImage(String url);
-
- /**
- * Starts an numbered list. Indentation will create nested items.
- */
- void insertOrderedList();
-
- /**
- * Starts an bulleted list. Indentation will create nested items.
- */
- void insertUnorderedList();
-
- /**
- * Is the current region bold?
- *
- * @return true if the current region is bold
- */
- boolean isBold();
-
- /**
- * Is the current region italic?
- *
- * @return true if the current region is italic
- */
- boolean isItalic();
-
- /**
- * Is the current region strikethrough?
- *
- * @return true if the current region is strikethrough
- */
- boolean isStrikethrough();
-
- /**
- * Is the current region subscript?
- *
- * @return true if the current region is subscript
- */
- boolean isSubscript();
-
- /**
- * Is the current region superscript?
- *
- * @return true if the current region is superscript
- */
- boolean isSuperscript();
-
- /**
- * Is the current region underlined?
- *
- * @return true if the current region is underlined
- */
- boolean isUnderlined();
-
- /**
- * Left indent.
- */
- void leftIndent();
-
- /**
- * Redo an action that was just undone.
- */
- void redo();
-
- /**
- * Removes all formatting on the selected text.
- */
- void removeFormat();
-
- /**
- * Removes any link from the selected text.
- */
- void removeLink();
-
- /**
- * Right indent.
- */
- void rightIndent();
-
- /**
- * Selects all the text.
- */
- void selectAll();
-
- /**
- * Sets the background color.
- *
- * @param color the new background color
- */
- void setBackColor(String color);
-
- /**
- * Sets the font name.
- *
- * @param name the new font name
- */
- void setFontName(String name);
-
- /**
- * Sets the font size.
- *
- * @param fontSize the new font size
- */
- void setFontSize(FontSize fontSize);
-
- /**
- * Sets the foreground color.
- *
- * @param color the new foreground color
- */
- void setForeColor(String color);
-
- /**
- * Sets the justification.
- *
- * @param justification the new justification
- */
- void setJustification(Justification justification);
-
- /**
- * Toggles bold.
- */
- void toggleBold();
-
- /**
- * Toggles italic.
- */
- void toggleItalic();
-
- /**
- * Toggles strikethrough.
- */
- void toggleStrikethrough();
-
- /**
- * Toggles subscript.
- */
- void toggleSubscript();
-
- /**
- * Toggles superscript.
- */
- void toggleSuperscript();
-
- /**
- * Toggles underline.
- */
- void toggleUnderline();
-
- /**
- * Undo the last action.
- */
- void undo();
- }
-
- /**
* Font size enumeration. Represents the seven basic HTML font sizes, as
* defined in CSS.
*/
public static class FontSize {
/**
- * Represents an XX-Small font.
- */
- public static final FontSize XX_SMALL = new FontSize(1);
-
- /**
- * Represents an X-Small font.
- */
- public static final FontSize X_SMALL = new FontSize(2);
-
- /**
- * Represents a Small font.
- */
- public static final FontSize SMALL = new FontSize(3);
-
- /**
- * Represents a Medium font.
- */
- public static final FontSize MEDIUM = new FontSize(4);
-
- /**
* Represents a Large font.
*/
public static final FontSize LARGE = new FontSize(5);
/**
+ * Represents a Medium font.
+ */
+ public static final FontSize MEDIUM = new FontSize(4);
+
+ /**
+ * Represents a Small font.
+ */
+ public static final FontSize SMALL = new FontSize(3);
+
+ /**
* Represents an X-Large font.
*/
public static final FontSize X_LARGE = new FontSize(6);
/**
+ * Represents an X-Small font.
+ */
+ public static final FontSize X_SMALL = new FontSize(2);
+
+ /**
* Represents an XX-Large font.
*/
public static final FontSize XX_LARGE = new FontSize(7);
+ /**
+ * Represents an XX-Small font.
+ */
+ public static final FontSize XX_SMALL = new FontSize(1);
+
private int number;
private FontSize(int number) {
@@ -531,6 +327,214 @@
}
/**
+ * <p>
+ * This interface is used to access full formatting options, when available.
+ * If the implementation supports full formatting, then
+ * {@link RichTextArea#getFormatter()} will return an instance of this class.
+ * </p>
+ * <p>
+ * The formatter will format the user selected text in the
+ * {@link RichTextArea}. As a result, it will only work reliably if the
+ * {@link RichTextArea} is attached, visible to on the page, and has been
+ * focused at least once. If you just want to initialize the content of the
+ * {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
+ * </p>
+ */
+ public interface Formatter extends ExtendedFormatter {
+ /**
+ * Creates a link to the supplied URL.
+ *
+ * @param url the URL to be linked to
+ */
+ void createLink(String url);
+
+ /**
+ * Gets the background color.
+ *
+ * @return the background color
+ */
+ String getBackColor();
+
+ /**
+ * Gets the foreground color.
+ *
+ * @return the foreground color
+ */
+ String getForeColor();
+
+ /**
+ * Inserts a horizontal rule.
+ */
+ void insertHorizontalRule();
+
+ /**
+ * Inserts generic html.
+ *
+ * @param html the HTML to insert
+ */
+ void insertHTML(String html);
+
+ /**
+ * Inserts an image element.
+ *
+ * @param url the url of the image to be inserted
+ */
+ void insertImage(String url);
+
+ /**
+ * Starts an numbered list. Indentation will create nested items.
+ */
+ void insertOrderedList();
+
+ /**
+ * Starts an bulleted list. Indentation will create nested items.
+ */
+ void insertUnorderedList();
+
+ /**
+ * Is the current region bold?
+ *
+ * @return true if the current region is bold
+ */
+ boolean isBold();
+
+ /**
+ * Is the current region italic?
+ *
+ * @return true if the current region is italic
+ */
+ boolean isItalic();
+
+ /**
+ * Is the current region strikethrough?
+ *
+ * @return true if the current region is strikethrough
+ */
+ boolean isStrikethrough();
+
+ /**
+ * Is the current region subscript?
+ *
+ * @return true if the current region is subscript
+ */
+ boolean isSubscript();
+
+ /**
+ * Is the current region superscript?
+ *
+ * @return true if the current region is superscript
+ */
+ boolean isSuperscript();
+
+ /**
+ * Is the current region underlined?
+ *
+ * @return true if the current region is underlined
+ */
+ boolean isUnderlined();
+
+ /**
+ * Left indent.
+ */
+ void leftIndent();
+
+ /**
+ * Redo an action that was just undone.
+ */
+ void redo();
+
+ /**
+ * Removes all formatting on the selected text.
+ */
+ void removeFormat();
+
+ /**
+ * Removes any link from the selected text.
+ */
+ void removeLink();
+
+ /**
+ * Right indent.
+ */
+ void rightIndent();
+
+ /**
+ * Selects all the text.
+ */
+ void selectAll();
+
+ /**
+ * Sets the background color.
+ *
+ * @param color the new background color
+ */
+ void setBackColor(String color);
+
+ /**
+ * Sets the font name.
+ *
+ * @param name the new font name
+ */
+ void setFontName(String name);
+
+ /**
+ * Sets the font size.
+ *
+ * @param fontSize the new font size
+ */
+ void setFontSize(FontSize fontSize);
+
+ /**
+ * Sets the foreground color.
+ *
+ * @param color the new foreground color
+ */
+ void setForeColor(String color);
+
+ /**
+ * Sets the justification.
+ *
+ * @param justification the new justification
+ */
+ void setJustification(Justification justification);
+
+ /**
+ * Toggles bold.
+ */
+ void toggleBold();
+
+ /**
+ * Toggles italic.
+ */
+ void toggleItalic();
+
+ /**
+ * Toggles strikethrough.
+ */
+ void toggleStrikethrough();
+
+ /**
+ * Toggles subscript.
+ */
+ void toggleSubscript();
+
+ /**
+ * Toggles superscript.
+ */
+ void toggleSuperscript();
+
+ /**
+ * Toggles underline.
+ */
+ void toggleUnderline();
+
+ /**
+ * Undo the last action.
+ */
+ void undo();
+ }
+
+ /**
* Justification enumeration. The three values are <code>left</code>,
* <code>right</code>, <code>center</code>.
*/
@@ -576,10 +580,15 @@
public RichTextArea() {
setElement(impl.getElement());
setStyleName("gwt-RichTextArea");
+ impl.setWidget(this);
+ }
+
+ public HandlerRegistration addInitializeHandler(InitializeHandler handler) {
+ return addHandler(handler, InitializeEvent.getType());
}
/**
- * Gets the basic rich text formatting interface. Note that formatting can
+ * Gets the basic rich text formatting interface. Note that formatting can
* only be done when the {@link RichTextArea} is attached, visible on the
* page, and has been focused by the user.
*
@@ -592,9 +601,9 @@
}
/**
- * Gets the full rich text formatting interface. Note that formatting can
- * only be done when the {@link RichTextArea} is attached, visible on the
- * page, and has been focused by the user.
+ * Gets the full rich text formatting interface. Note that formatting can only
+ * be done when the {@link RichTextArea} is attached, visible on the page, and
+ * has been focused by the user.
*
* @return <code>null</code> if full formatting is not supported
* @deprecated use {@link #getFormatter()} instead
diff --git a/user/src/com/google/gwt/user/client/ui/RootPanel.java b/user/src/com/google/gwt/user/client/ui/RootPanel.java
index 96999c4..ac2797a 100644
--- a/user/src/com/google/gwt/user/client/ui/RootPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/RootPanel.java
@@ -63,6 +63,17 @@
}
}
+ /**
+ * The singleton command used to detach widgets.
+ */
+ private static final AttachDetachException.Command maybeDetachCommand = new AttachDetachException.Command() {
+ public void execute(Widget w) {
+ if (w.isAttached()) {
+ w.onDetach();
+ }
+ }
+ };
+
private static Map<String, RootPanel> rootPanels = new HashMap<String, RootPanel>();
private static Set<Widget> widgetsToDetach = new HashSet<Widget>();
@@ -90,8 +101,11 @@
assert widgetsToDetach.contains(widget) : "detachNow() called on a widget "
+ "not currently in the detach list";
- widget.onDetach();
- widgetsToDetach.remove(widget);
+ try {
+ widget.onDetach();
+ } finally {
+ widgetsToDetach.remove(widget);
+ }
}
/**
@@ -222,19 +236,17 @@
// When the window is closing, detach all widgets that need to be
// cleaned up. This will cause all of their event listeners
// to be unhooked, which will avoid potential memory leaks.
- for (Widget widget : widgetsToDetach) {
- if (widget.isAttached()) {
- widget.onDetach();
- }
+ try {
+ AttachDetachException.tryCommand(widgetsToDetach, maybeDetachCommand);
+ } finally {
+ widgetsToDetach.clear();
+
+ // Clear the RootPanel cache, since we've "detached" all RootPanels at
+ // this point. This would be pointless, since it only happens on unload,
+ // but it is very helpful for unit tests, because it allows
+ // RootPanel.get() to work properly even after a synthesized "unload".
+ rootPanels.clear();
}
-
- widgetsToDetach.clear();
-
- // Clear the RootPanel cache, since we've "detached" all RootPanels at this
- // point. This would be pointless, since it only happens on unload, but it
- // is very helpful for unit tests, because it allows RootPanel.get() to work
- // properly even after a synthesized "unload".
- rootPanels.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 6ed6a1b..b77ea37 100644
--- a/user/src/com/google/gwt/user/client/ui/SimplePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SimplePanel.java
@@ -103,13 +103,15 @@
}
// Orphan.
- orphan(w);
-
- // Physical detach.
- getContainerElement().removeChild(w.getElement());
-
- // Logical detach.
- widget = null;
+ try {
+ orphan(w);
+ } finally {
+ // Physical detach.
+ getContainerElement().removeChild(w.getElement());
+
+ // Logical detach.
+ widget = null;
+ }
return true;
}
diff --git a/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
index 46a43e8..7f6f3d8 100644
--- a/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
@@ -212,10 +212,10 @@
}
@Override
- public void add(Widget child, Direction direction, double size) {
- super.add(child, direction, size);
+ public void insert(Widget child, Direction direction, double size, Widget before) {
+ super.insert(child, direction, size, before);
if (direction != Direction.CENTER) {
- addSplitter();
+ insertSplitter(before);
}
}
@@ -251,13 +251,26 @@
splitter.setMinSize(minSize);
}
- private void addSplitter() {
+ private Splitter getAssociatedSplitter(Widget child) {
+ // If a widget has a next sibling, it must be a splitter, because the only
+ // widget that *isn't* followed by a splitter must be the CENTER, which has
+ // no associated splitter.
+ int idx = getWidgetIndex(child);
+ if (idx < getWidgetCount() - 2) {
+ Widget splitter = getWidget(idx + 1);
+ assert splitter instanceof Splitter : "Expected child widget to be splitter";
+ return (Splitter) splitter;
+ }
+ return null;
+ }
+
+ private void insertSplitter(Widget before) {
assert getChildren().size() > 0 : "Can't add a splitter before any children";
assert getCenter() == null : "Can't add a splitter after the CENTER widget";
Widget lastChild = getChildren().get(getChildren().size() - 1);
LayoutData lastChildLayout = (LayoutData) lastChild.getLayoutData();
- Splitter splitter;
+ Splitter splitter = null;
switch (lastChildLayout.direction) {
case WEST:
splitter = new HSplitter(lastChild, false);
@@ -273,22 +286,8 @@
break;
default:
assert false : "Unexpected direction";
- return;
}
- super.add(splitter, lastChildLayout.direction, SPLITTER_SIZE);
- }
-
- private Splitter getAssociatedSplitter(Widget child) {
- // If a widget has a next sibling, it must be a splitter, because the only
- // widget that *isn't* followed by a splitter must be the CENTER, which has
- // no associated splitter.
- int idx = getWidgetIndex(child);
- if (idx < getWidgetCount() - 2) {
- Widget splitter = getWidget(idx + 1);
- assert splitter instanceof Splitter : "Expected child widget to be splitter";
- return (Splitter) splitter;
- }
- return null;
+ super.insert(splitter, lastChildLayout.direction, SPLITTER_SIZE, before);
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/SplitPanel.java b/user/src/com/google/gwt/user/client/ui/SplitPanel.java
index aaf5692..70e7b44 100644
--- a/user/src/com/google/gwt/user/client/ui/SplitPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SplitPanel.java
@@ -404,12 +404,16 @@
// Remove the old child.
if (oldWidget != null) {
// Orphan old.
- orphan(oldWidget);
- // Physical detach old.
- DOM.removeChild(elements[index], oldWidget.getElement());
+ try {
+ orphan(oldWidget);
+ } finally {
+ // Physical detach old.
+ DOM.removeChild(elements[index], oldWidget.getElement());
+ widgets[index] = null;
+ }
}
- // Logical detach old / attach new.
+ // Logical attach new.
widgets[index] = w;
if (w != null) {
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 75f0f6e..70b1e73 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -718,22 +718,22 @@
@Override
protected void doAttachChildren() {
- // Ensure that all child widgets are attached.
- for (Iterator<Widget> it = iterator(); it.hasNext();) {
- Widget child = it.next();
- child.onAttach();
+ try {
+ AttachDetachException.tryCommand(this,
+ AttachDetachException.attachCommand);
+ } finally {
+ DOM.setEventListener(focusable, this);
}
- DOM.setEventListener(focusable, this);
}
@Override
protected void doDetachChildren() {
- // Ensure that all child widgets are detached.
- for (Iterator<Widget> it = iterator(); it.hasNext();) {
- Widget child = it.next();
- child.onDetach();
+ try {
+ AttachDetachException.tryCommand(this,
+ AttachDetachException.detachCommand);
+ } finally {
+ DOM.setEventListener(focusable, null);
}
- DOM.setEventListener(focusable, null);
}
/**
@@ -816,10 +816,12 @@
assert (widget.getParent() == this);
// Orphan.
- widget.setParent(null);
-
- // Logical detach.
- childWidgets.remove(widget);
+ try {
+ widget.setParent(null);
+ } finally {
+ // Logical detach.
+ childWidgets.remove(widget);
+ }
}
/**
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 eb01ed0..73804da 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -577,12 +577,15 @@
// Detach old child from tree.
if (widget != null) {
- if (tree != null) {
- tree.orphan(widget);
+ try {
+ if (tree != null) {
+ tree.orphan(widget);
+ }
+ } finally {
+ // Physical detach old child.
+ contentElem.removeChild(widget.getElement());
+ widget = null;
}
-
- // Physical detach old child.
- contentElem.removeChild(widget.getElement());
}
// Clear out any existing content before adding a widget.
diff --git a/user/src/com/google/gwt/user/client/ui/Widget.java b/user/src/com/google/gwt/user/client/ui/Widget.java
index dcc0a73..147c8be 100644
--- a/user/src/com/google/gwt/user/client/ui/Widget.java
+++ b/user/src/com/google/gwt/user/client/ui/Widget.java
@@ -251,10 +251,16 @@
}
/**
+ * <p>
* This method is called when a widget is attached to the browser's document.
* To receive notification after a Widget has been added to the document,
* override the {@link #onLoad} method.
- *
+ * </p>
+ * <p>
+ * It is strongly recommended that you override {@link #onLoad()} or
+ * {@link #doAttachChildren()} instead of this method to avoid
+ * inconsistencies between logical and physical attachment states.
+ * </p>
* <p>
* Subclasses that override this method must call
* <code>super.onAttach()</code> to ensure that the Widget has been attached
@@ -262,6 +268,8 @@
* </p>
*
* @throws IllegalStateException if this widget is already attached
+ * @see #onLoad()
+ * @see #doAttachChildren()
*/
protected void onAttach() {
if (isAttached()) {
@@ -287,10 +295,16 @@
}
/**
+ * <p>
* This method is called when a widget is detached from the browser's
* document. To receive notification before a Widget is removed from the
* document, override the {@link #onUnload} method.
- *
+ * </p>
+ * <p>
+ * It is strongly recommended that you override {@link #onUnload()} or
+ * {@link #doDetachChildren()} instead of this method to avoid
+ * inconsistencies between logical and physical attachment states.
+ * </p>
* <p>
* Subclasses that override this method must call
* <code>super.onDetach()</code> to ensure that the Widget has been detached
@@ -300,6 +314,8 @@
* </p>
*
* @throws IllegalStateException if this widget is already detached
+ * @see #onUnload()
+ * @see #doDetachChildren()
*/
protected void onDetach() {
if (!isAttached()) {
@@ -313,9 +329,13 @@
onUnload();
} finally {
// Put this in a finally, just in case onUnload throws an exception.
- doDetachChildren();
- DOM.setEventListener(getElement(), null);
- attached = false;
+ try {
+ doDetachChildren();
+ } finally {
+ // Put this in a finally, in case doDetachChildren throws an exception.
+ DOM.setEventListener(getElement(), null);
+ attached = false;
+ }
}
}
@@ -377,12 +397,16 @@
void setParent(Widget parent) {
Widget oldParent = this.parent;
if (parent == null) {
- if (oldParent != null && oldParent.isAttached()) {
- onDetach();
- assert !isAttached() : "Failure of " + this.getClass().getName()
- + " to call super.onDetach()";
+ try {
+ if (oldParent != null && oldParent.isAttached()) {
+ onDetach();
+ assert !isAttached() : "Failure of " + this.getClass().getName()
+ + " to call super.onDetach()";
+ }
+ } finally {
+ // Put this in a finally in case onDetach throws an exception.
+ this.parent = null;
}
- this.parent = null;
} else {
if (oldParent != null) {
throw new IllegalStateException(
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java
index 0228633..e2e70f4 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java
@@ -15,13 +15,15 @@
*/
package com.google.gwt.user.client.ui.impl;
+import com.google.gwt.event.logical.shared.InitializeEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.RichTextArea;
/**
- * Base class for RichText platform implementations. The default version
- * simply creates a text area with no rich text support.
+ * Base class for RichText platform implementations. The default version simply
+ * creates a text area with no rich text support.
*
* This is not currently used by any user-agent, but will provide a
* <textarea> fallback in the event a future browser fails to implement
@@ -30,6 +32,7 @@
public class RichTextAreaImpl {
protected Element elem;
+ protected RichTextArea richTextWidget;
public RichTextAreaImpl() {
elem = createElement();
@@ -56,7 +59,7 @@
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.focus();
} else {
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.blur();
- }
+ }
}-*/;
public void setHTML(String html) {
@@ -67,6 +70,10 @@
DOM.setElementProperty(elem, "value", text);
}
+ public void setWidget(RichTextArea richTextWidget) {
+ this.richTextWidget = richTextWidget;
+ }
+
public void uninitElement() {
}
@@ -76,10 +83,13 @@
protected void hookEvents() {
DOM.sinkEvents(elem, Event.MOUSEEVENTS | Event.KEYEVENTS | Event.ONCHANGE
- | Event.ONCLICK | Event.FOCUSEVENTS);
+ | Event.ONCLICK | Event.FOCUSEVENTS);
}
protected void onElementInitialized() {
hookEvents();
+ if (richTextWidget != null) {
+ InitializeEvent.fire(richTextWidget);
+ }
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
index d4e99d1..86dac58 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
@@ -334,13 +334,13 @@
initializing = false;
isReady = true;
- super.onElementInitialized();
-
// When the iframe is ready, ensure cached content is set.
if (beforeInitPlaceholder != null) {
setHTMLImpl(DOM.getInnerHTML(beforeInitPlaceholder));
beforeInitPlaceholder = null;
}
+
+ super.onElementInitialized();
}
protected native void setHTMLImpl(String html) /*-{
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
index 363da9c..0b08854 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
@@ -26,7 +27,10 @@
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* The entry point class for GWTTestCases.
@@ -41,32 +45,56 @@
* The RPC callback object for {@link GWTRunner#junitHost}. When
* {@link #onSuccess(Object)} is called, it's time to run the next test case.
*/
- private final class JUnitHostListener implements AsyncCallback<TestInfo[]> {
+ private final class JUnitHostListener implements AsyncCallback<TestBlock> {
+
+ /**
+ * The number of times we've failed to communicate with the server on the
+ * current test batch.
+ */
+ private int curRetryCount = 0;
/**
* A call to junitHost failed.
*/
public void onFailure(Throwable caught) {
- // Try the call again
- new Timer() {
- @Override
- public void run() {
- syncToServer();
- }
- }.schedule(1000);
+ if (maxRetryCount < 0 || curRetryCount < maxRetryCount) {
+ // Try the call again
+ curRetryCount++;
+ new Timer() {
+ @Override
+ public void run() {
+ syncToServer();
+ }
+ }.schedule(1000);
+ } else {
+ // Give up and mark the test complete on the client side.
+ markComplete();
+ }
}
/**
* A call to junitHost succeeded; run the next test case.
*/
- public void onSuccess(TestInfo[] nextTestBlock) {
+ public void onSuccess(TestBlock nextTestBlock) {
+ curRetryCount = 0;
currentBlock = nextTestBlock;
- currentBlockIndex = 0;
+ currentTestIndex = 0;
currentResults.clear();
- if (currentBlock != null && currentBlock.length > 0) {
+ if (currentBlock != null && currentBlock.getTests().length > 0) {
doRunTest();
+ } else {
+ markComplete();
}
}
+
+ /**
+ * Set a global expando so the test infrastructure knows that the test is
+ * complete.
+ */
+ private native void markComplete() /*-{
+ $doc.title = "Completed Tests";
+ $wnd._gwt_test_complete = true;
+ }-*/;
}
/**
@@ -84,6 +112,17 @@
*/
private static final String TESTFUNC_QUERY_PARAM = "gwt.junit.testfuncname";
+ /**
+ * A query param specifying the number of times to retry if the server fails
+ * to respond.
+ */
+ private static final String RETRYCOUNT_QUERY_PARAM = "gwt.junit.retrycount";
+
+ /**
+ * A query param specifying the block index to start on.
+ */
+ private static final String BLOCKINDEX_QUERY_PARAM = "gwt.junit.blockindex";
+
public static GWTRunner get() {
return sInstance;
}
@@ -91,12 +130,12 @@
/**
* The current block of tests to execute.
*/
- private TestInfo currentBlock[];
+ private TestBlock currentBlock;
/**
* Active test within current block of tests.
*/
- private int currentBlockIndex = 0;
+ private int currentTestIndex = 0;
/**
* Results for all test cases in the current block.
@@ -104,6 +143,11 @@
private HashMap<TestInfo, JUnitResult> currentResults = new HashMap<TestInfo, JUnitResult>();
/**
+ * If set, all remaining tests will fail with the failure message.
+ */
+ private String failureMessage;
+
+ /**
* The remote service to communicate with.
*/
private final JUnitHostAsync junitHost = (JUnitHostAsync) GWT.create(JUnitHost.class);
@@ -114,6 +158,12 @@
private final JUnitHostListener junitHostListener = new JUnitHostListener();
/**
+ * The maximum number of times to retry communication with the server per
+ * test batch.
+ */
+ private int maxRetryCount = -1;
+
+ /**
* If true, run a single test case with no RPC.
*/
private boolean serverless = false;
@@ -133,6 +183,7 @@
}
public void onModuleLoad() {
+ maxRetryCount = parseQueryParamInteger(RETRYCOUNT_QUERY_PARAM, -1);
currentBlock = checkForQueryParamTestToRun();
if (currentBlock != null) {
/*
@@ -154,10 +205,14 @@
// That's it, we're done
return;
}
+ if (result != null && failureMessage != null) {
+ RuntimeException ex = new RuntimeException(failureMessage);
+ result.setExceptionWrapper(new ExceptionWrapper(ex));
+ }
TestInfo currentTest = getCurrentTest();
currentResults.put(currentTest, result);
- ++currentBlockIndex;
- if (currentBlockIndex < currentBlock.length) {
+ ++currentTestIndex;
+ if (currentTestIndex < currentBlock.getTests().length) {
// Run the next test after a short delay.
DeferredCommand.addCommand(new Command() {
public void execute() {
@@ -175,15 +230,22 @@
*/
protected abstract GWTTestCase createNewTestCase(String testClass);
- private TestInfo[] checkForQueryParamTestToRun() {
+ /**
+ * Implemented by the generated subclass. Get the value of the user agent
+ * property.
+ */
+ protected abstract String getUserAgentProperty();
+
+ private TestBlock checkForQueryParamTestToRun() {
String testClass = Window.Location.getParameter(TESTCLASS_QUERY_PARAM);
String testMethod = Window.Location.getParameter(TESTFUNC_QUERY_PARAM);
if (testClass == null || testMethod == null) {
return null;
}
// TODO: support blocks of tests?
- return new TestInfo[] {new TestInfo(GWT.getModuleName(), testClass,
- testMethod)};
+ TestInfo[] tests = new TestInfo[] {new TestInfo(GWT.getModuleName(),
+ testClass, testMethod)};
+ return new TestBlock(tests, 0);
}
private void doRunTest() {
@@ -198,18 +260,59 @@
* We're being asked to run a test in a different module. We must navigate
* the browser to a new URL which will run that other module.
*/
- String href = Window.Location.getHref();
- String newHref = href.replace(currentModule, newModule);
- Window.Location.replace(newHref);
+ Map<String, List<String>> paramMap = new HashMap<String, List<String>>();
+ paramMap.putAll(Window.Location.getParameterMap());
+ paramMap.put(BLOCKINDEX_QUERY_PARAM, Arrays.asList(String.valueOf(
+ currentBlock.getIndex())));
+ paramMap.put(RETRYCOUNT_QUERY_PARAM, Arrays.asList(String.valueOf(
+ maxRetryCount)));
+ // TODO(jat): there really ought to be some library code for dealing
+ // with URLs rather than having to do this.
+ String path = Window.Location.getPath().replace(currentModule, newModule);
+ StringBuilder query = new StringBuilder();
+ char prefix = '?';
+ for (Map.Entry<String, List<String>> entry : paramMap.entrySet()) {
+ for (String val : entry.getValue()) {
+ query.append(prefix).append(entry.getKey()).append('=').append(val);
+ prefix = '&';
+ }
+ }
+ String newUrl = Window.Location.getProtocol() + "//"
+ + Window.Location.getHost() + path + query
+ + Window.Location.getHash();
+ Window.Location.replace(newUrl);
currentBlock = null;
- currentBlockIndex = 0;
+ currentTestIndex = 0;
}
}
private TestInfo getCurrentTest() {
- return currentBlock[currentBlockIndex];
+ return currentBlock.getTests()[currentTestIndex];
}
+ /**
+ * Parse an integer from a query parameter, returning the default value if
+ * the parameter cannot be found.
+ *
+ * @param paramName the parameter name
+ * @param defaultValue the default value
+ * @return the integer value of the parameter
+ */
+ private int parseQueryParamInteger(String paramName, int defaultValue) {
+ String value = Window.Location.getParameter(paramName);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ setFailureMessage("'" + value + "' is not a valid value for " +
+ paramName + ".");
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+
private void runTest() {
// Dynamically create a new test case.
TestInfo currentTest = getCurrentTest();
@@ -233,11 +336,22 @@
testCase.__doRunTest();
}
+ /**
+ * Fail all tests with the specified message.
+ */
+ private void setFailureMessage(String message) {
+ failureMessage = message;
+ }
+
private void syncToServer() {
if (currentBlock == null) {
- junitHost.getFirstMethod(junitHostListener);
+ int firstBlockIndex = parseQueryParamInteger(BLOCKINDEX_QUERY_PARAM, 0);
+ junitHost.getTestBlock(firstBlockIndex, getUserAgentProperty(),
+ junitHostListener);
} else {
- junitHost.reportResultsAndGetNextMethod(currentResults, junitHostListener);
+ junitHost.reportResultsAndGetTestBlock(currentResults,
+ currentBlock.getIndex() + 1, getUserAgentProperty(),
+ junitHostListener);
}
}
diff --git a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
index 8d6eb5b..352c676 100644
--- a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
+++ b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
@@ -16,7 +16,6 @@
package com.google.gwt.dev.shell.rewrite.client;
import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.Timer;
import junit.framework.TestCase;
@@ -109,10 +108,11 @@
@com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1e foo");
}-*/;
};
+ e.foo();
}
public void test2() {
- SecondTopLevelClass second = new SecondTopLevelClass();
+// SecondTopLevelClass second = new SecondTopLevelClass();
SecondTopLevelClass.InnerClass.test();
}
@@ -138,6 +138,7 @@
private static int logCount = 0;
+ @SuppressWarnings("unused") // used by JSNI
private static void log(String msg) {
assertEquals(messages[logCount++], msg);
}
@@ -171,6 +172,7 @@
* Added a test3() method so that when the test fails, it fails quickly.
* Instead of timing out, the AssertEquals fails
*/
+ @SuppressWarnings("unused")
private void test3() {
class Foo {
public native void foo() /*-{
@@ -248,6 +250,7 @@
@com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2e foo");
}-*/;
};
+ e.foo();
}
}
@@ -271,28 +274,17 @@
private static int logCount = 0;
+ @SuppressWarnings("unused") // called by JSNI
private static void log(String msg) {
assertEquals(messages[logCount++], msg);
}
void test() {
- Timer t1 = new Timer() {
- @Override
- public void run() {
- }
- };
-
EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() {
public native void foo() /*-{
@com.google.gwt.dev.shell.rewrite.client.ThirdTopLevelClass::log(Ljava/lang/String;)("3a foo");
}-*/;
};
a.foo();
-
- Timer t2 = new Timer() {
- @Override
- public void run() {
- }
- };
}
}
diff --git a/user/test/com/google/gwt/junit/BatchingStrategyTest.java b/user/test/com/google/gwt/junit/BatchingStrategyTest.java
new file mode 100644
index 0000000..bd86c18
--- /dev/null
+++ b/user/test/com/google/gwt/junit/BatchingStrategyTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests of {@link BatchingStrategy}.
+ */
+public class BatchingStrategyTest extends TestCase {
+
+ /**
+ * The synthetic name of the module used for this test.
+ */
+ private static final String FAKE_MODULE_SYNTHETIC_NAME = "FakeModuleName.Fake";
+
+ /**
+ * The fake key in {@link GWTTestCase#ALL_GWT_TESTS}.
+ */
+ private static TestModuleInfo fakeModuleInfo;
+
+ private static final TestInfo TEST_INFO_0_0 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass0", "testMethod0");
+ private static final TestInfo TEST_INFO_0_1 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass0", "testMethod1");
+ private static final TestInfo TEST_INFO_1_2 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass1", "testMethod2");
+ private static final TestInfo TEST_INFO_1_3 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass1", "testMethod3");
+ private static final TestInfo TEST_INFO_1_4 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass1", "testMethod4");
+ private static final TestInfo TEST_INFO_2_5 = new TestInfo(
+ FAKE_MODULE_SYNTHETIC_NAME, "testClass2", "testMethod5");
+
+ public void testClassBatchingStrategy() {
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ testBlocks.add(new TestInfo[] {TEST_INFO_0_0, TEST_INFO_0_1});
+ testBlocks.add(new TestInfo[] {TEST_INFO_1_2, TEST_INFO_1_3, TEST_INFO_1_4});
+ testBlocks.add(new TestInfo[] {TEST_INFO_2_5});
+ testBatchingStrategy(new ClassBatchingStrategy(), testBlocks);
+ }
+
+ public void testModuleBatchingStrategy() {
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ testBlocks.add(new TestInfo[] {
+ TEST_INFO_0_0, TEST_INFO_0_1, TEST_INFO_1_2, TEST_INFO_1_3,
+ TEST_INFO_1_4, TEST_INFO_2_5});
+ testBatchingStrategy(new ModuleBatchingStrategy(), testBlocks);
+ }
+
+ public void testNoBatchingStrategy() {
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ testBlocks.add(new TestInfo[] {TEST_INFO_0_0});
+ testBlocks.add(new TestInfo[] {TEST_INFO_0_1});
+ testBlocks.add(new TestInfo[] {TEST_INFO_1_2});
+ testBlocks.add(new TestInfo[] {TEST_INFO_1_3});
+ testBlocks.add(new TestInfo[] {TEST_INFO_1_4});
+ testBlocks.add(new TestInfo[] {TEST_INFO_2_5});
+ testBatchingStrategy(new NoBatchingStrategy(), testBlocks);
+ }
+
+ /**
+ * Add a fake entry to {@link GWTTestCase#ALL_GWT_TESTS} for testing.
+ */
+ private void populateAllGwtTests() {
+ if (fakeModuleInfo == null) {
+ fakeModuleInfo = new TestModuleInfo("FakeModuleName",
+ FAKE_MODULE_SYNTHETIC_NAME, null);
+ Set<TestInfo> tests = fakeModuleInfo.getTests();
+ tests.add(TEST_INFO_0_0);
+ tests.add(TEST_INFO_0_1);
+ tests.add(TEST_INFO_1_2);
+ tests.add(TEST_INFO_1_3);
+ tests.add(TEST_INFO_1_4);
+ tests.add(TEST_INFO_2_5);
+ }
+ GWTTestCase.ALL_GWT_TESTS.put(fakeModuleInfo.getSyntheticModuleName(),
+ fakeModuleInfo);
+ }
+
+ /**
+ * Remove the fake entry in {@link GWTTestCase#ALL_GWT_TESTS} for testing.
+ */
+ private void depopulateAllGwtTests() {
+ GWTTestCase.ALL_GWT_TESTS.remove(FAKE_MODULE_SYNTHETIC_NAME);
+ }
+
+ private void testBatchingStrategy(BatchingStrategy strategy,
+ List<TestInfo[]> expectedBlocks) {
+ try {
+ populateAllGwtTests();
+
+ List<TestInfo[]> actualBlocks = strategy.getTestBlocks(FAKE_MODULE_SYNTHETIC_NAME);
+ assertEquals(expectedBlocks.size(), actualBlocks.size());
+ for (int i = 0; i < expectedBlocks.size(); i++) {
+ TestInfo[] expectedBlock = expectedBlocks.get(i);
+ TestInfo[] actualBlock = actualBlocks.get(i);
+ assertEquals(expectedBlock.length, actualBlock.length);
+ for (int j = 0; j < expectedBlock.length; j++) {
+ assertEquals(expectedBlock[j], actualBlock[j]);
+ }
+ }
+ } finally {
+ depopulateAllGwtTests();
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/junit/BenchmarkNoClientTest.java b/user/test/com/google/gwt/junit/BenchmarkNoClientTest.java
new file mode 100644
index 0000000..fda6793
--- /dev/null
+++ b/user/test/com/google/gwt/junit/BenchmarkNoClientTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.junit;
+
+import com.google.gwt.benchmarks.client.Benchmark;
+import com.google.gwt.junit.JUnitShell.Strategy;
+
+/**
+ * Tests for {@link Benchmark} that cannot run on the client.
+ */
+public class BenchmarkNoClientTest extends GWTTestCaseNoClientTest {
+
+ /**
+ * A mock version of the {@link Benchmark} used for testing.
+ */
+ private static class MockBenchmark extends Benchmark {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.mock.Mock";
+ }
+ }
+
+ @Override
+ public void testGetStrategy() {
+ Benchmark testCase = new MockBenchmark();
+ Strategy strategy = testCase.getStrategy();
+ assertEquals("com.google.gwt.benchmarks.Benchmarks",
+ strategy.getModuleInherit());
+ assertEquals("Benchmarks", strategy.getSyntheticModuleExtension());
+ assertEquals("com.google.gwt.mock.Mock.Benchmarks",
+ testCase.getSyntheticModuleName());
+ }
+}
diff --git a/user/test/com/google/gwt/junit/GWTTestCaseNoClientTest.java b/user/test/com/google/gwt/junit/GWTTestCaseNoClientTest.java
new file mode 100644
index 0000000..c25473f
--- /dev/null
+++ b/user/test/com/google/gwt/junit/GWTTestCaseNoClientTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.junit.JUnitShell.Strategy;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link GWTTestCase} that cannot be run on the client because they
+ * have dependencies on non-client code.
+ */
+public class GWTTestCaseNoClientTest extends TestCase {
+
+ /**
+ * A mock version of the {@link GWTTestCase} used for testing.
+ */
+ private static class MockGWTTestCase extends GWTTestCase {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.mock.Mock";
+ }
+ }
+
+ public void testGetStrategy() {
+ GWTTestCase testCase = new MockGWTTestCase();
+ Strategy strategy = testCase.getStrategy();
+ assertEquals("com.google.gwt.junit.JUnit", strategy.getModuleInherit());
+ assertEquals("JUnit", strategy.getSyntheticModuleExtension());
+ assertEquals("com.google.gwt.mock.Mock.JUnit",
+ testCase.getSyntheticModuleName());
+ }
+}
diff --git a/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
new file mode 100644
index 0000000..0d34966
--- /dev/null
+++ b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
@@ -0,0 +1,451 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.junit.client.impl.JUnitResult;
+import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests of {@link JUnitMessageQueue}.
+ */
+public class JUnitMessageQueueTest extends TestCase {
+
+ public void testAddTestBlocks() {
+ JUnitMessageQueue queue = new JUnitMessageQueue(10);
+ assertEquals(0, queue.getTestBlocks().size());
+ List<TestInfo[]> expectedBlocks = new ArrayList<TestInfo[]>();
+
+ // Add some blocks.
+ {
+ List<TestInfo[]> testBlocks = createTestBlocks(5, 3);
+ queue.addTestBlocks(testBlocks, false);
+ expectedBlocks.addAll(testBlocks);
+ assertEquals(expectedBlocks, queue.getTestBlocks());
+ }
+
+ // Try to add an empty block.
+ {
+ List<TestInfo[]> testBlocks = createTestBlocks(5, 0);
+ try {
+ queue.addTestBlocks(testBlocks, false);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ assertEquals(expectedBlocks, queue.getTestBlocks());
+ }
+
+ // Add last block.
+ {
+ List<TestInfo[]> testBlocks = createTestBlocks(3, 1);
+ queue.addTestBlocks(testBlocks, true);
+ expectedBlocks.addAll(testBlocks);
+ assertEquals(expectedBlocks, queue.getTestBlocks());
+ }
+
+ // Try to add more blocks.
+ {
+ List<TestInfo[]> testBlocks = createTestBlocks(1, 1);
+ try {
+ queue.addTestBlocks(testBlocks, false);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected.
+ }
+ assertEquals(expectedBlocks, queue.getTestBlocks());
+ }
+ }
+
+ public void testGetNumConnectedClients() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(15, 1, 1);
+ assertEquals(0, queue.getNumConnectedClients());
+
+ // Add some clients in a few ways.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertEquals(3, queue.getNumConnectedClients());
+ }
+
+ // Add duplicate clients.
+ {
+ queue.getTestBlock("client3", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client3", "ie6", null);
+ queue.reportResults("client4", "safari", createTestResults(0));
+ assertEquals(5, queue.getNumConnectedClients());
+ }
+
+ // Add existing clients.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertEquals(5, queue.getNumConnectedClients());
+ }
+ }
+
+ public void testGetNumRetrievedClients() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(15, 2, 3);
+ TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
+ TestInfo test0_0 = testBlock0[0];
+ TestInfo test0_1 = testBlock0[1];
+ TestInfo test0_2 = testBlock0[2];
+ TestInfo[] testBlock1 = queue.getTestBlocks().get(1);
+ TestInfo test1_0 = testBlock1[0];
+ TestInfo test1_1 = testBlock1[1];
+ TestInfo test1_2 = testBlock1[2];
+ assertEquals(0, queue.getNumClientsRetrievedTest(test0_0));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test0_1));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test0_2));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_0));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_1));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_2));
+
+ // First client retrieves the first test block.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ assertEquals(1, queue.getNumClientsRetrievedTest(test0_0));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test0_1));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test0_2));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_0));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_1));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_2));
+ }
+
+ // Second client retrieves the first test block.
+ {
+ queue.getTestBlock("client1", "ie6", 0, timeout);
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_0));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_1));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_2));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_0));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_1));
+ assertEquals(0, queue.getNumClientsRetrievedTest(test1_2));
+ }
+
+ // First client retrieves the second test block.
+ {
+ queue.getTestBlock("client0", "ie6", 1, timeout);
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_0));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_1));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_2));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_0));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_1));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_2));
+ }
+
+ // First client retrieves the second test block again.
+ {
+ queue.getTestBlock("client0", "ie6", 1, timeout);
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_0));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_1));
+ assertEquals(2, queue.getNumClientsRetrievedTest(test0_2));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_0));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_1));
+ assertEquals(1, queue.getNumClientsRetrievedTest(test1_2));
+ }
+ }
+
+ public void testGetResults() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(3, 2, 3);
+ TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
+ TestInfo test0_0 = testBlock0[0];
+
+ // The results from the three clients.
+ JUnitResult result0 = new JUnitResult();
+ result0.setException(new IllegalArgumentException("0"));
+ JUnitResult result1 = new JUnitResult();
+ result0.setException(new IllegalArgumentException("1"));
+ JUnitResult result2 = new JUnitResult();
+ result0.setException(new IllegalArgumentException("2"));
+
+ // Client 0 reports results for first test case.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, result0);
+ queue.reportResults("client0", "ie6", results);
+ }
+
+ // Client 1 reports results for first test case.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, result1);
+ queue.reportResults("client1", "ie6", results);
+ }
+
+ // Client 2 reports results for first test case.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, result2);
+ queue.reportResults("client2", "ie6", results);
+ }
+
+ // Get the results
+ Map<String, JUnitResult> results = queue.getResults(test0_0);
+ assertEquals(result0, results.get("client0"));
+ assertEquals(result1, results.get("client1"));
+ assertEquals(result2, results.get("client2"));
+ }
+
+ public void testGetTestBlock() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(15, 2, 3);
+ TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
+ TestInfo[] testBlock1 = queue.getTestBlocks().get(1);
+
+ // Get the first test block.
+ {
+ TestBlock block = queue.getTestBlock("client0", "ie6", 0, timeout);
+ assertEquals(testBlock0, block.getTests());
+ assertEquals(0, block.getIndex());
+ }
+
+ // Get the second test block.
+ {
+ TestBlock block = queue.getTestBlock("client0", "ie6", 1, timeout);
+ assertEquals(testBlock1, block.getTests());
+ assertEquals(1, block.getIndex());
+ }
+
+ // Get the third test block.
+ {
+ assertNull(queue.getTestBlock("client0", "ie6", 2, timeout));
+ }
+ }
+
+ public void testGetUserAgents() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(15, 1, 1);
+ assertEquals(0, queue.getUserAgents().length);
+
+ // Add some clients in a few ways.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertSimilar(new String[] {"ie6", "gecko", "safari"},
+ queue.getUserAgents());
+ }
+
+ // Add duplicate clients.
+ {
+ queue.getTestBlock("client3", "ie7", 0, timeout);
+ queue.reportFatalLaunch("client3", "ie7", null);
+ queue.reportResults("client4", "gecko1_8", createTestResults(0));
+ queue.getTestBlock("client3", "ie7", 0, timeout);
+ assertSimilar(new String[] {"ie6", "ie7", "gecko", "gecko1_8", "safari"},
+ queue.getUserAgents());
+ }
+
+ // Add existing clients.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertSimilar(new String[] {"ie6", "ie7", "gecko", "gecko1_8", "safari"},
+ queue.getUserAgents());
+ }
+ }
+
+ public void testHasResults() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(3, 2, 3);
+ TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
+ TestInfo test0_0 = testBlock0[0];
+ TestInfo test0_1 = testBlock0[1];
+ TestInfo test0_2 = testBlock0[2];
+ TestInfo[] testBlock1 = queue.getTestBlocks().get(1);
+ TestInfo test1_0 = testBlock1[0];
+ TestInfo test1_1 = testBlock1[1];
+ TestInfo test1_2 = testBlock1[2];
+ assertFalse(queue.hasResults(test0_0));
+ assertFalse(queue.hasResults(test0_1));
+ assertFalse(queue.hasResults(test0_2));
+ assertFalse(queue.hasResults(test1_0));
+ assertFalse(queue.hasResults(test1_1));
+ assertFalse(queue.hasResults(test1_2));
+
+ // First client reports results for the first test.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, new JUnitResult());
+ queue.reportResults("client0", "ie6", results);
+ assertFalse(queue.hasResults(test0_0));
+ assertFalse(queue.hasResults(test0_1));
+ assertFalse(queue.hasResults(test0_2));
+ assertFalse(queue.hasResults(test1_0));
+ assertFalse(queue.hasResults(test1_1));
+ assertFalse(queue.hasResults(test1_2));
+ }
+
+ // Second client reports results for the first test.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, new JUnitResult());
+ queue.reportResults("client1", "ie6", results);
+ assertFalse(queue.hasResults(test0_0));
+ assertFalse(queue.hasResults(test0_1));
+ assertFalse(queue.hasResults(test0_2));
+ assertFalse(queue.hasResults(test1_0));
+ assertFalse(queue.hasResults(test1_1));
+ assertFalse(queue.hasResults(test1_2));
+ }
+
+ // First client reports results for the second test.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_1, new JUnitResult());
+ queue.reportResults("client0", "ie6", results);
+ assertFalse(queue.hasResults(test0_0));
+ assertFalse(queue.hasResults(test0_1));
+ assertFalse(queue.hasResults(test0_2));
+ assertFalse(queue.hasResults(test1_0));
+ assertFalse(queue.hasResults(test1_1));
+ assertFalse(queue.hasResults(test1_2));
+ }
+
+ // Third client reports results for the first test.
+ {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ results.put(test0_0, new JUnitResult());
+ queue.reportResults("client2", "ie6", results);
+ assertTrue(queue.hasResults(test0_0));
+ assertFalse(queue.hasResults(test0_1));
+ assertFalse(queue.hasResults(test0_2));
+ assertFalse(queue.hasResults(test1_0));
+ assertFalse(queue.hasResults(test1_1));
+ assertFalse(queue.hasResults(test1_2));
+ }
+ }
+
+ public void testNewClients() {
+ final long timeout = System.currentTimeMillis() + 15;
+ JUnitMessageQueue queue = createQueue(15, 1, 1);
+ assertEquals(0, queue.getNewClients().length);
+
+ // Add some clients in a few ways.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertSimilar(new String[] {"client0", "client1", "client2"},
+ queue.getNewClients());
+ assertEquals(0, queue.getNewClients().length);
+ }
+
+ // Add duplicate clients.
+ {
+ queue.getTestBlock("client3", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client3", "ie6", null);
+ queue.reportResults("client4", "safari", createTestResults(0));
+ queue.getTestBlock("client3", "ie6", 0, timeout);
+ assertSimilar(new String[] {"client3", "client4"}, queue.getNewClients());
+ assertEquals(0, queue.getNewClients().length);
+ }
+
+ // Add existing clients.
+ {
+ queue.getTestBlock("client0", "ie6", 0, timeout);
+ queue.reportFatalLaunch("client1", "gecko", null);
+ queue.reportResults("client2", "safari", createTestResults(0));
+ assertEquals(0, queue.getNewClients().length);
+ }
+ }
+
+ /**
+ * Assert that two arrays are the same size and contain the same elements.
+ * Ordering does not matter.
+ *
+ * @param expected the expected array
+ * @param actual the actual array
+ */
+ private void assertSimilar(String[] expected, String[] actual) {
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ String expectedItem = expected[i];
+ boolean matched = false;
+ for (int j = 0; j < actual.length; j++) {
+ if (expectedItem == actual[j]) {
+ matched = true;
+ }
+ }
+ assertTrue(matched);
+ }
+ }
+
+ /**
+ * Create a {@link JUnitMessageQueue} with the specified number of blocks.
+ *
+ * @param numClients the number of remote clients
+ * @param numBlocks the number of test blocks to add
+ * @param testsPerBlock the number of tests per block
+ * @return the message queue
+ */
+ private JUnitMessageQueue createQueue(int numClients, int numBlocks,
+ int testsPerBlock) {
+ JUnitMessageQueue queue = new JUnitMessageQueue(numClients);
+ queue.addTestBlocks(createTestBlocks(numBlocks, testsPerBlock), true);
+ return queue;
+ }
+
+ /**
+ * Create a list of test blocks.
+ *
+ * @param numBlocks the number of test blocks to add
+ * @param testsPerBlock the number of tests per block
+ * @return the test blocks
+ */
+ private List<TestInfo[]> createTestBlocks(int numBlocks, int testsPerBlock) {
+ List<TestInfo[]> testBlocks = new ArrayList<TestInfo[]>();
+ for (int i = 0; i < numBlocks; i++) {
+ TestInfo[] testBlock = new TestInfo[testsPerBlock];
+ for (int test = 0; test < testsPerBlock; test++) {
+ testBlock[test] = new TestInfo("testModule" + i, "testClass",
+ "testMethod" + test);
+ }
+ testBlocks.add(testBlock);
+ }
+ return testBlocks;
+ }
+
+ /**
+ * Create some fake test results.
+ *
+ * @param numTests the number of results to generate
+ * @return the test results
+ */
+ private Map<TestInfo, JUnitResult> createTestResults(int numTests) {
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ for (int i = 0; i < numTests; i++) {
+ TestInfo testInfo = new TestInfo("testModule", "testClass", "testMethod"
+ + i);
+ results.put(testInfo, new JUnitResult());
+ }
+ return results;
+ }
+}
diff --git a/user/test/com/google/gwt/junit/JUnitSuite.java b/user/test/com/google/gwt/junit/JUnitSuite.java
index a8807eb..ec1c25b 100644
--- a/user/test/com/google/gwt/junit/JUnitSuite.java
+++ b/user/test/com/google/gwt/junit/JUnitSuite.java
@@ -28,6 +28,10 @@
GWTTestSuite suite = new GWTTestSuite("Test for suite for com.google.gwt.junit");
suite.addTestSuite(FakeMessagesMakerTest.class);
+ suite.addTestSuite(BatchingStrategyTest.class);
+ suite.addTestSuite(JUnitMessageQueueTest.class);
+ suite.addTestSuite(GWTTestCaseNoClientTest.class);
+ suite.addTestSuite(BenchmarkNoClientTest.class);
// client
// Suppressed due to flakiness on Linux
diff --git a/user/test/com/google/gwt/junit/JUnitTest.gwt.xml b/user/test/com/google/gwt/junit/JUnitTest.gwt.xml
new file mode 100644
index 0000000..b9cd010
--- /dev/null
+++ b/user/test/com/google/gwt/junit/JUnitTest.gwt.xml
@@ -0,0 +1,17 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+</module>
diff --git a/user/test/com/google/gwt/junit/JUnitTest2.gwt.xml b/user/test/com/google/gwt/junit/JUnitTest2.gwt.xml
new file mode 100644
index 0000000..b9cd010
--- /dev/null
+++ b/user/test/com/google/gwt/junit/JUnitTest2.gwt.xml
@@ -0,0 +1,17 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+</module>
diff --git a/user/test/com/google/gwt/junit/NonGwtTestSuite.java b/user/test/com/google/gwt/junit/NonGwtTestSuite.java
new file mode 100644
index 0000000..f1f4833
--- /dev/null
+++ b/user/test/com/google/gwt/junit/NonGwtTestSuite.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.junit.client.ModuleOneTest;
+import com.google.gwt.junit.client.ModuleOneTest2;
+import com.google.gwt.junit.client.ModuleTwoTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests that a normal test suite will run even if modules are out of order.
+ */
+public class NonGwtTestSuite {
+
+ public static Test suite() {
+ // This is intentionally not a GWTTestSuite.
+ TestSuite suite = new TestSuite();
+
+ suite.addTestSuite(ModuleOneTest.class);
+ suite.addTestSuite(ModuleTwoTest.class);
+ suite.addTestSuite(ModuleOneTest2.class);
+
+ return suite;
+ }
+}
diff --git a/user/test/com/google/gwt/junit/client/ModuleOneTest.java b/user/test/com/google/gwt/junit/client/ModuleOneTest.java
new file mode 100644
index 0000000..1d1c5a7
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ModuleOneTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit.client;
+
+/**
+ * A test in the first module.
+ */
+public class ModuleOneTest extends GWTTestCase {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.junit.JUnitTest";
+ }
+
+ public void testTrue() {
+ assertTrue(true);
+ }
+}
diff --git a/user/test/com/google/gwt/junit/client/ModuleOneTest2.java b/user/test/com/google/gwt/junit/client/ModuleOneTest2.java
new file mode 100644
index 0000000..f0fc271
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ModuleOneTest2.java
@@ -0,0 +1,30 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit.client;
+
+/**
+ * Another test in the first module.
+ */
+public class ModuleOneTest2 extends GWTTestCase {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.junit.JUnitTest";
+ }
+
+ public void testTrue() {
+ assertTrue(true);
+ }
+}
diff --git a/user/test/com/google/gwt/junit/client/ModuleTwoTest.java b/user/test/com/google/gwt/junit/client/ModuleTwoTest.java
new file mode 100644
index 0000000..0d983dc
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ModuleTwoTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.junit.client;
+
+/**
+ * A test in the second module.
+ */
+public class ModuleTwoTest extends GWTTestCase {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.junit.JUnitTest2";
+ }
+
+ public void testTrue() {
+ assertTrue(true);
+ }
+}
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 235b5e6..bed64f3 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -54,6 +54,7 @@
import com.google.gwt.user.client.ui.HiddenTest;
import com.google.gwt.user.client.ui.HistoryTest;
import com.google.gwt.user.client.ui.HorizontalPanelTest;
+import com.google.gwt.user.client.ui.HorizontalSplitPanelTest;
import com.google.gwt.user.client.ui.HyperlinkTest;
import com.google.gwt.user.client.ui.ImageTest;
import com.google.gwt.user.client.ui.LazyPanelTest;
@@ -61,7 +62,6 @@
import com.google.gwt.user.client.ui.ListBoxTest;
import com.google.gwt.user.client.ui.MenuBarTest;
import com.google.gwt.user.client.ui.NamedFrameTest;
-import com.google.gwt.user.client.ui.PanelTest;
import com.google.gwt.user.client.ui.PopupTest;
import com.google.gwt.user.client.ui.PrefixTreeTest;
import com.google.gwt.user.client.ui.RadioButtonTest;
@@ -70,7 +70,6 @@
import com.google.gwt.user.client.ui.ScrollPanelTest;
import com.google.gwt.user.client.ui.SimpleCheckBoxTest;
import com.google.gwt.user.client.ui.SimpleRadioButtonTest;
-import com.google.gwt.user.client.ui.SplitPanelTest;
import com.google.gwt.user.client.ui.StackPanelTest;
import com.google.gwt.user.client.ui.SuggestBoxTest;
import com.google.gwt.user.client.ui.TabBarTest;
@@ -80,6 +79,7 @@
import com.google.gwt.user.client.ui.TreeTest;
import com.google.gwt.user.client.ui.UIObjectTest;
import com.google.gwt.user.client.ui.VerticalPanelTest;
+import com.google.gwt.user.client.ui.VerticalSplitPanelTest;
import com.google.gwt.user.client.ui.WidgetCollectionTest;
import com.google.gwt.user.client.ui.WidgetIteratorsTest;
import com.google.gwt.user.client.ui.WidgetOnLoadTest;
@@ -139,6 +139,7 @@
suite.addTestSuite(HiddenTest.class);
suite.addTestSuite(HistoryTest.class);
suite.addTestSuite(HorizontalPanelTest.class);
+ suite.addTestSuite(HorizontalSplitPanelTest.class);
suite.addTestSuite(HTMLPanelTest.class);
suite.addTestSuite(HyperlinkTest.class);
suite.addTestSuite(ImageBundleGeneratorTest.class);
@@ -148,7 +149,6 @@
suite.addTestSuite(ListBoxTest.class);
suite.addTestSuite(MenuBarTest.class);
suite.addTestSuite(NamedFrameTest.class);
- suite.addTestSuite(PanelTest.class);
suite.addTestSuite(PopupTest.class);
suite.addTestSuite(PrefixTreeTest.class);
suite.addTestSuite(RadioButtonTest.class);
@@ -156,7 +156,6 @@
suite.addTestSuite(ScrollPanelTest.class);
suite.addTestSuite(SimpleCheckBoxTest.class);
suite.addTestSuite(SimpleRadioButtonTest.class);
- suite.addTestSuite(SplitPanelTest.class);
suite.addTestSuite(StackPanelTest.class);
suite.addTestSuite(SuggestBoxTest.class);
suite.addTestSuite(TabBarTest.class);
@@ -166,6 +165,7 @@
suite.addTestSuite(TreeItemTest.class);
suite.addTestSuite(UIObjectTest.class);
suite.addTestSuite(VerticalPanelTest.class);
+ suite.addTestSuite(VerticalSplitPanelTest.class);
suite.addTestSuite(WidgetCollectionTest.class);
suite.addTestSuite(WidgetIteratorsTest.class);
suite.addTestSuite(WidgetOnLoadTest.class);
diff --git a/user/test/com/google/gwt/user/client/EventTest.java b/user/test/com/google/gwt/user/client/EventTest.java
index 6490c58..4997547 100644
--- a/user/test/com/google/gwt/user/client/EventTest.java
+++ b/user/test/com/google/gwt/user/client/EventTest.java
@@ -326,6 +326,29 @@
/**
* Test that {@link Event#fireNativePreviewEvent(NativeEvent)} returns the
+ * correct value even if another event is fired while handling the current
+ * event.
+ */
+ public void testFireNativePreviewEventWithInterupt() {
+ NativePreviewHandler handler = new NativePreviewHandler() {
+ private boolean first = true;
+
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (first) {
+ event.cancel();
+ first = false;
+ assertTrue(Event.fireNativePreviewEvent(null));
+ assertTrue(event.isCanceled());
+ }
+ }
+ };
+ HandlerRegistration reg = Event.addNativePreviewHandler(handler);
+ assertFalse(Event.fireNativePreviewEvent(null));
+ reg.removeHandler();
+ }
+
+ /**
+ * Test that {@link Event#fireNativePreviewEvent(NativeEvent)} returns the
* correct value if the native event is not canceled.
*/
public void testFireNativePreviewEventWithoutCancel() {
@@ -524,6 +547,6 @@
}
private native boolean isInternetExplorer() /*-{
- return navigator.userAgent.toLowerCase().indexOf("msie") != -1;
- }-*/;
+ return navigator.userAgent.toLowerCase().indexOf("msie") != -1;
+ }-*/;
}
diff --git a/user/test/com/google/gwt/user/client/ui/AbsolutePanelTest.java b/user/test/com/google/gwt/user/client/ui/AbsolutePanelTest.java
index 0d2da55..1635e9c 100644
--- a/user/test/com/google/gwt/user/client/ui/AbsolutePanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/AbsolutePanelTest.java
@@ -18,21 +18,12 @@
import com.google.gwt.dom.client.Document;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
-import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Window;
/**
* Tests for {@link AbsolutePanel}.
*/
-public class AbsolutePanelTest extends GWTTestCase {
-
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new AbsolutePanel());
- }
+public class AbsolutePanelTest extends PanelTestBase<AbsolutePanel> {
/**
* AbsolutePanel once had a bug where calling
@@ -82,4 +73,9 @@
3 + 100, absX);
assertEquals(7 + 200, absY);
}
+
+ @Override
+ protected AbsolutePanel createPanel() {
+ return new AbsolutePanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/AbstractCellPanelTest.java b/user/test/com/google/gwt/user/client/ui/AbstractCellPanelTest.java
index 0eeed2a..55f5ebe 100644
--- a/user/test/com/google/gwt/user/client/ui/AbstractCellPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/AbstractCellPanelTest.java
@@ -16,12 +16,13 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.junit.client.GWTTestCase;
/**
* Base tests for {@link CellPanel}.
+ *
+ * @param <T> the panel type
*/
-public abstract class AbstractCellPanelTest extends GWTTestCase {
+public abstract class AbstractCellPanelTest<T extends CellPanel> extends PanelTestBase<T> {
@Override
public String getModuleName() {
diff --git a/user/test/com/google/gwt/user/client/ui/BadWidget.java b/user/test/com/google/gwt/user/client/ui/BadWidget.java
new file mode 100644
index 0000000..7d2889f
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/BadWidget.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A widget that throws an exception onLoad or onUnload.
+ */
+class BadWidget extends Widget {
+ /**
+ * Wrap an existing div.
+ *
+ * @param element the div to wrap
+ * @return a {@link BadWidget}
+ */
+ public static BadWidget wrap(Element element) {
+ // Assert that the element is attached.
+ assert Document.get().getBody().isOrHasChild(element);
+
+ BadWidget widget = new BadWidget(element);
+
+ // Mark it attached and remember it for cleanup.
+ widget.onAttach();
+ RootPanel.detachOnWindowClose(widget);
+
+ return widget;
+ }
+
+ private boolean failOnLoad;
+ private boolean failOnUnload;
+ private boolean failAttachChildren;
+ private boolean failDetachChildren;
+
+ public BadWidget() {
+ this(Document.get().createDivElement());
+ }
+
+ protected BadWidget(Element element) {
+ setElement(element);
+ assert element.getTagName().equalsIgnoreCase("div");
+ }
+
+ @Override
+ public void onLoad() {
+ if (failOnLoad) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void onUnload() {
+ if (failOnUnload) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public void setFailAttachChildren(boolean fail) {
+ this.failAttachChildren = fail;
+ }
+
+ public void setFailDetachChildren(boolean fail) {
+ this.failDetachChildren = fail;
+ }
+
+ public void setFailOnLoad(boolean fail) {
+ this.failOnLoad = fail;
+ }
+
+ public void setFailOnUnload(boolean fail) {
+ this.failOnUnload = fail;
+ }
+
+ @Override
+ protected void doAttachChildren() {
+ if (failAttachChildren) {
+ Set<Throwable> cause = new HashSet<Throwable>();
+ cause.add(new IllegalArgumentException());
+ throw new AttachDetachException(cause);
+ }
+ }
+
+ @Override
+ protected void doDetachChildren() {
+ if (failDetachChildren) {
+ Set<Throwable> cause = new HashSet<Throwable>();
+ cause.add(new IllegalArgumentException());
+ throw new AttachDetachException(cause);
+ }
+ }
+}
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 5e38593..7dff6fb 100644
--- a/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/CaptionPanelTest.java
@@ -17,6 +17,7 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.ui.HasWidgetsTester.WidgetAdder;
/**
* Tests {@link CaptionPanel}.
@@ -29,14 +30,17 @@
}
public void testHasWidgets() {
+ WidgetAdder adder = new HasWidgetsTester.DefaultWidgetAdder();
+
// With no caption.
- HasWidgetsTester.testAll(new CaptionPanel());
+ HasWidgetsTester.testAll(new CaptionPanel(), adder, false);
// With a text caption.
- HasWidgetsTester.testAll(new CaptionPanel("some text"));
+ HasWidgetsTester.testAll(new CaptionPanel("some text"), adder, false);
// With a complex HTML caption.
- HasWidgetsTester.testAll(new CaptionPanel("<legend>not the <i>actual</i> legend<legend>", true));
+ HasWidgetsTester.testAll(new CaptionPanel(
+ "<legend>not the <i>actual</i> legend<legend>", true), adder, false);
}
public void testCaptionAcceptsEmptyStringAndRemovesLegendElement() {
diff --git a/user/test/com/google/gwt/user/client/ui/DeckPanelTest.java b/user/test/com/google/gwt/user/client/ui/DeckPanelTest.java
index 7e11c10..be086cf 100644
--- a/user/test/com/google/gwt/user/client/ui/DeckPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DeckPanelTest.java
@@ -18,16 +18,11 @@
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
-import com.google.gwt.junit.client.GWTTestCase;
/**
* Test for {@link DeckPanel}.
*/
-public class DeckPanelTest extends GWTTestCase {
- @Override
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
+public class DeckPanelTest extends PanelTestBase<DeckPanel> {
/**
* Test that the {@link DeckPanel} calls widget.setVisible(true) on the
@@ -108,4 +103,9 @@
// Verify content.onLoad was actually called
assertEquals("attached", content.getText());
}
+
+ @Override
+ protected DeckPanel createPanel() {
+ return new DeckPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/DecoratorPanelTest.java b/user/test/com/google/gwt/user/client/ui/DecoratorPanelTest.java
index 23187e7..d7b5c23 100644
--- a/user/test/com/google/gwt/user/client/ui/DecoratorPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DecoratorPanelTest.java
@@ -16,14 +16,14 @@
package com.google.gwt.user.client.ui;
-import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
/**
* Test for {@link DecoratorPanel}.
*/
-public class DecoratorPanelTest extends GWTTestCase {
+public class DecoratorPanelTest extends SimplePanelTestBase<DecoratorPanel> {
+
/**
* Assert that an element has the specified class name.
*
@@ -34,11 +34,6 @@
assertEquals(styleName, DOM.getElementProperty(elem, "className"));
}
- @Override
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
/**
* Test addition and removal of widgets.
*/
@@ -96,4 +91,9 @@
// Check the container element
assertTrue(panel.getCellElement(1, 1) == panel.getContainerElement());
}
+
+ @Override
+ protected DecoratorPanel createPanel() {
+ return new DecoratorPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java b/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
index 41c8828..8e12014 100644
--- a/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
@@ -63,7 +63,8 @@
}
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new DisclosurePanel());
+ HasWidgetsTester.testAll(new DisclosurePanel(),
+ new HasWidgetsTester.DefaultWidgetAdder(), false);
}
public void testDebugId() {
diff --git a/user/test/com/google/gwt/user/client/ui/DockPanelTest.java b/user/test/com/google/gwt/user/client/ui/DockPanelTest.java
index 543d714..85bf22b 100644
--- a/user/test/com/google/gwt/user/client/ui/DockPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DockPanelTest.java
@@ -114,7 +114,7 @@
}
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new DockPanel(), new Adder());
+ HasWidgetsTester.testAll(new DockPanel(), new Adder(), true);
}
public void testDebugId() {
diff --git a/user/test/com/google/gwt/user/client/ui/FlowPanelTest.java b/user/test/com/google/gwt/user/client/ui/FlowPanelTest.java
index 5db2691..c74f789 100644
--- a/user/test/com/google/gwt/user/client/ui/FlowPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/FlowPanelTest.java
@@ -16,23 +16,15 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.junit.client.GWTTestCase;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* Tests the FlowPanel widget.
*/
-public class FlowPanelTest extends GWTTestCase {
-
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new FlowPanel());
- }
+public class FlowPanelTest extends PanelTestBase<FlowPanel> {
public void testClear() {
int size = 10;
@@ -60,6 +52,45 @@
}
}
+ public void testClearWithError() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget badWidget = new BadWidget();
+ badWidget.setFailOnUnload(true);
+ Label label0 = new Label();
+ Label label1 = new Label();
+
+ // Add the widget to a panel.
+ FlowPanel panel = createPanel();
+ panel.add(label0);
+ panel.add(badWidget);
+ panel.add(label1);
+ assertFalse(label0.isAttached());
+ assertFalse(badWidget.isAttached());
+ assertFalse(label1.isAttached());
+
+ // Attach the widget.
+ RootPanel.get().add(panel);
+ assertTrue(label0.isAttached());
+ assertTrue(badWidget.isAttached());
+ assertTrue(label1.isAttached());
+
+ // Remove the widget from the panel.
+ try {
+ panel.clear();
+ } catch (AttachDetachException e) {
+ // Expected.
+ Set<Throwable> causes = e.getCauses();
+ assertEquals(1, causes.size());
+ Throwable[] throwables = causes.toArray(new Throwable[1]);
+ assertTrue(throwables[0] instanceof IllegalArgumentException);
+ }
+ assertFalse(label0.isAttached());
+ assertFalse(badWidget.isAttached());
+ assertFalse(label1.isAttached());
+ assertNull(badWidget.getParent());
+ assertNull(badWidget.getElement().getParentElement());
+ }
+
public void testClearWithNestedChildren() {
FlowPanel target = new FlowPanel();
FlowPanel child0 = new FlowPanel();
@@ -77,4 +108,9 @@
assertEquals(child1Elem, child0Elem.getFirstChildElement());
}
+
+ @Override
+ protected FlowPanel createPanel() {
+ return new FlowPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/FocusPanelTest.java b/user/test/com/google/gwt/user/client/ui/FocusPanelTest.java
index 9784269..c35a8ee 100644
--- a/user/test/com/google/gwt/user/client/ui/FocusPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/FocusPanelTest.java
@@ -15,18 +15,13 @@
*/
package com.google.gwt.user.client.ui;
-import com.google.gwt.junit.client.GWTTestCase;
-
/**
* Test the FocusPanel widget.
*/
-public class FocusPanelTest extends GWTTestCase {
+public class FocusPanelTest extends SimplePanelTestBase<FocusPanel> {
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new FocusPanel());
+ @Override
+ protected FocusPanel createPanel() {
+ return new FocusPanel();
}
}
diff --git a/user/test/com/google/gwt/user/client/ui/FormPanelTest.java b/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
index bd8684a..e016b1d 100644
--- a/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
@@ -19,18 +19,16 @@
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
-import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
-import com.google.gwt.user.client.ui.HasWidgetsTester.WidgetAdder;
/**
* Tests the FormPanel.
*
* @see com.google.gwt.user.server.ui.FormPanelTestServlet
*/
-public class FormPanelTest extends GWTTestCase {
+public class FormPanelTest extends SimplePanelTestBase<FormPanel> {
public static boolean clicked = false;
@Override
@@ -38,10 +36,6 @@
return "com.google.gwt.user.FormPanelTest";
}
- public void testAttachDetachOrder(HasWidgets container, WidgetAdder adder) {
- HasWidgetsTester.testAll(new FormPanel());
- }
-
public void testCancelSubmit() {
TextBox tb = new TextBox();
tb.setName("q");
@@ -351,6 +345,11 @@
form.submit();
}
+ @Override
+ protected FormPanel createPanel() {
+ return new FormPanel();
+ }
+
private native boolean isHappyDivPresent(Element iframe) /*-{
return !!iframe.contentWindow.document.getElementById(':)');
}-*/;
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
index 94fa845..13993e8 100644
--- a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
@@ -75,7 +75,7 @@
*/
public void testAttachDetachOrder() {
HTMLPanel p = new HTMLPanel("<div id='w00t'></div>");
- HasWidgetsTester.testAll(p, new Adder());
+ HasWidgetsTester.testAll(p, new Adder(), true);
}
/**
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLTableTestBase.java b/user/test/com/google/gwt/user/client/ui/HTMLTableTestBase.java
index 9e31ea3..c8467e0 100644
--- a/user/test/com/google/gwt/user/client/ui/HTMLTableTestBase.java
+++ b/user/test/com/google/gwt/user/client/ui/HTMLTableTestBase.java
@@ -62,7 +62,7 @@
public abstract HTMLTable getTable(int row, int column);
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(getTable(1, 1), new Adder());
+ HasWidgetsTester.testAll(getTable(1, 1), new Adder(), true);
}
public void testBoundsOnEmptyTable() {
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 8771888..4bbbb9c 100644
--- a/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java
+++ b/user/test/com/google/gwt/user/client/ui/HasWidgetsTester.java
@@ -19,6 +19,9 @@
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.
@@ -43,7 +46,7 @@
* Default implementation used by containers for which
* {@link HasWidgets#add(Widget)} will not throw an exception.
*/
- private static class DefaultWidgetAdder implements WidgetAdder {
+ static class DefaultWidgetAdder implements WidgetAdder {
public void addChild(HasWidgets container, Widget child) {
container.add(child);
}
@@ -54,17 +57,21 @@
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()));
+ 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()));
+ Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(),
+ getElement()));
}
}
@@ -88,8 +95,24 @@
* @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);
}
/**
@@ -113,6 +136,8 @@
* *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);
@@ -122,12 +147,123 @@
TestWidget widget = new TestWidget();
adder.addChild(container, widget);
Assert.assertTrue(widget.isAttached());
- Assert.assertTrue(DOM.isOrHasChild(RootPanel.getBodyElement(), widget.getElement()));
+ 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()));
+ }
+
+ /**
+ * 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());
}
/**
@@ -137,7 +273,41 @@
* @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);
+ }
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/HorizontalPanelTest.java b/user/test/com/google/gwt/user/client/ui/HorizontalPanelTest.java
index fb81b11..47bcf79 100644
--- a/user/test/com/google/gwt/user/client/ui/HorizontalPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HorizontalPanelTest.java
@@ -22,11 +22,7 @@
/**
* A test for {@link HorizontalPanel}.
*/
-public class HorizontalPanelTest extends AbstractCellPanelTest {
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new HorizontalPanel());
- }
+public class HorizontalPanelTest extends AbstractCellPanelTest<HorizontalPanel> {
public void testDebugId() {
HorizontalPanel p = new HorizontalPanel();
@@ -91,4 +87,9 @@
p.add(new Label("c"));
return p;
}
+
+ @Override
+ protected HorizontalPanel createPanel() {
+ return new HorizontalPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/HorizontalSplitPanelTest.java b/user/test/com/google/gwt/user/client/ui/HorizontalSplitPanelTest.java
new file mode 100644
index 0000000..babd80b
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/HorizontalSplitPanelTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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
+ * 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;
+
+/**
+ * Tests for {@link HorizontalSplitPanel}.
+ */
+public class HorizontalSplitPanelTest extends
+ SplitPanelTestBase<HorizontalSplitPanel> {
+
+ public void testDebugId() {
+ HorizontalSplitPanel hSplit = new HorizontalSplitPanel();
+ hSplit.ensureDebugId("hsplit");
+ Label left = new Label("left");
+ hSplit.setLeftWidget(left);
+ Label right = new Label("right");
+ hSplit.setRightWidget(right);
+ UIObjectTest.assertDebugId("hsplit", hSplit.getElement());
+ UIObjectTest.assertDebugId("hsplit-left", DOM.getParent(left.getElement()));
+ UIObjectTest.assertDebugId("hsplit-right",
+ DOM.getParent(right.getElement()));
+ }
+
+ @Override
+ protected HorizontalSplitPanel createPanel() {
+ return new HorizontalSplitPanel();
+ }
+
+ @Override
+ protected Widget getEndOfLineWidget(HorizontalSplitPanel split) {
+ return split.getEndOfLineWidget();
+ }
+
+ @Override
+ protected Widget getStartOfLineWidget(HorizontalSplitPanel split) {
+ return split.getStartOfLineWidget();
+ }
+
+ @Override
+ protected void setEndOfLineWidget(HorizontalSplitPanel split, Widget w) {
+ split.setEndOfLineWidget(w);
+ }
+
+ @Override
+ protected void setStartOfLineWidget(HorizontalSplitPanel split, Widget w) {
+ split.setStartOfLineWidget(w);
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/PanelTest.java b/user/test/com/google/gwt/user/client/ui/PanelTest.java
deleted file mode 100644
index d3e32e9..0000000
--- a/user/test/com/google/gwt/user/client/ui/PanelTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.junit.client.GWTTestCase;
-
-/**
- * Panel test.
- * TODO: add circular containment test.
- */
-public class PanelTest extends GWTTestCase {
-
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
- public void testOnAttach() {
- // Used to call onDetach when not attached.
- Widget someWidget = new TextBox();
- Panel panel1 = new SimplePanel(); // new and unattached
- Panel panel2 = new SimplePanel(); // new and unattached
- panel1.add(someWidget);
- panel2.add(someWidget);
-
- // Make sure that the RootPanel does not throw an exception.
- RootPanel.get().setParent(null);
- RootPanel.get().setParent(null);
- }
-}
diff --git a/user/test/com/google/gwt/user/client/ui/PanelTestBase.java b/user/test/com/google/gwt/user/client/ui/PanelTestBase.java
new file mode 100644
index 0000000..9ebdeb6
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/PanelTestBase.java
@@ -0,0 +1,84 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+/**
+ * Base tests for classes that extend {@link Panel}
+ *
+ * TODO: add circular containment test.
+ *
+ * @param <T> the panel type
+ */
+public abstract class PanelTestBase<T extends Panel> extends WidgetTestBase {
+
+ public void testAttachDetachOrder() {
+ HasWidgetsTester.testAll(createPanel(),
+ new HasWidgetsTester.DefaultWidgetAdder(), supportsMultipleWidgets());
+ }
+
+ public void testOnAttach() {
+ // Used to call onDetach when not attached.
+ Widget someWidget = new TextBox();
+ Panel panel1 = createPanel(); // new and unattached
+ Panel panel2 = createPanel(); // new and unattached
+ panel1.add(someWidget);
+ panel2.add(someWidget);
+
+ // Make sure that the RootPanel does not throw an exception.
+ RootPanel.get().setParent(null);
+ RootPanel.get().setParent(null);
+ }
+
+ public void testRemoveWithError() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget badWidget = new BadWidget();
+ badWidget.setFailOnUnload(true);
+
+ // Add the widget to a panel.
+ Panel panel = createPanel();
+ panel.add(badWidget);
+ assertFalse(badWidget.isAttached());
+
+ // Attach the widget.
+ RootPanel.get().add(panel);
+ assertTrue(badWidget.isAttached());
+
+ // Remove the widget from the panel.
+ try {
+ panel.remove(badWidget);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(badWidget.isAttached());
+ assertNull(badWidget.getParent());
+ assertNull(badWidget.getElement().getParentElement());
+
+ // Detach the panel to ensure that it doesn't throw an exception.
+ RootPanel.get().remove(panel);
+ }
+
+ protected abstract T createPanel();
+
+ /**
+ * Check if the panel in test supports multiple (unbounded) widgets.
+ *
+ * @return true if multiple widgets are supported
+ */
+ protected boolean supportsMultipleWidgets() {
+ return true;
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/PopupTest.java b/user/test/com/google/gwt/user/client/ui/PopupTest.java
index fa58883..0ec6231 100644
--- a/user/test/com/google/gwt/user/client/ui/PopupTest.java
+++ b/user/test/com/google/gwt/user/client/ui/PopupTest.java
@@ -31,6 +31,15 @@
public class PopupTest extends GWTTestCase {
/**
+ * The Widget adder used to set the widget in a {@link PopupPanel}.
+ */
+ private static class Adder implements HasWidgetsTester.WidgetAdder {
+ public void addChild(HasWidgets container, Widget child) {
+ ((PopupPanel) container).setWidget(child);
+ }
+ }
+
+ /**
* Expose otherwise private or protected methods.
*/
private static class TestablePopupPanel extends PopupPanel {
@@ -101,6 +110,10 @@
assertFalse(popup.isShowing());
}
+ public void testAttachDetachOrder() {
+ HasWidgetsTester.testAll(createPopupPanel(), new Adder(), false);
+ }
+
public void testAutoHidePartner() {
final PopupPanel popup = new PopupPanel();
diff --git a/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java b/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
index c2a6fc4..cce59a8 100644
--- a/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
+++ b/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
@@ -16,6 +16,8 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.logical.shared.InitializeEvent;
+import com.google.gwt.event.logical.shared.InitializeHandler;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
@@ -26,7 +28,8 @@
* Tests the {@link RichTextArea} widget.
*/
public class RichTextAreaTest extends GWTTestCase {
-
+ static final int RICH_TEXT_ASYNC_DELAY = 3000;
+
@Override
public String getModuleName() {
return "com.google.gwt.user.User";
@@ -37,17 +40,12 @@
* IE actually preserves dynamically-created iframe contents across DOM
* removal/re-adding).
*/
+ @DoNotRunWith(Platform.Htmlunit)
public void testAddEditRemoveAdd() {
final RichTextArea area = new RichTextArea();
- RootPanel.get().add(area);
- area.setHTML("foo");
-
- // This has to be done on a timer because the rta can take some time to
- // finish initializing (on some browsers).
- this.delayTestFinish(1000);
- new Timer() {
- @Override
- public void run() {
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ area.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
RootPanel.get().remove(area);
RootPanel.get().add(area);
@@ -56,7 +54,9 @@
assertEquals("foo", area.getHTML());
finishTest();
}
- }.schedule(500);
+ });
+ RootPanel.get().add(area);
+ area.setHTML("foo");
}
/**
@@ -92,14 +92,10 @@
@DoNotRunWith(Platform.Htmlunit)
public void testFormatAfterInitialize() {
final RichTextArea area = new RichTextArea();
- RootPanel.get().add(area);
- // This has to be done on a timer because the rta can take some time to
- // finish initializing (on some browsers).
- this.delayTestFinish(1000);
- new Timer() {
- @Override
- public void run() {
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ area.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
BasicFormatter formatter = area.getBasicFormatter();
if (formatter != null) {
formatter.toggleBold();
@@ -107,7 +103,8 @@
RootPanel.get().remove(area);
finishTest();
}
- }.schedule(500);
+ });
+ RootPanel.get().add(area);
}
public void testFormatBeforeAttach() {
@@ -132,14 +129,9 @@
@DoNotRunWith(Platform.Htmlunit)
public void testFormatWhenHidden() {
final RichTextArea area = new RichTextArea();
- RootPanel.get().add(area);
-
- // This has to be done on a timer because the rta can take some time to
- // finish initializing (on some browsers).
- this.delayTestFinish(1000);
- new Timer() {
- @Override
- public void run() {
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ area.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
area.setVisible(false);
BasicFormatter formatter = area.getBasicFormatter();
if (formatter != null) {
@@ -149,80 +141,101 @@
RootPanel.get().remove(area);
finishTest();
}
- }.schedule(500);
+ });
+ RootPanel.get().add(area);
+ }
+
+ /**
+ * See that the custom InitializeEvent fires.
+ */
+ @DoNotRunWith(Platform.Htmlunit)
+ public void testRichTextInitializeEvent() {
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ final RichTextArea richTextArea = new RichTextArea();
+ richTextArea.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
+ finishTest();
+ }
+ });
+ RootPanel.get().add(richTextArea);
}
/**
* Test that a delayed set of HTML is reflected. Some platforms have timing
* subtleties that need to be tested.
*/
+ @DoNotRunWith(Platform.Htmlunit)
public void testSetHTMLAfterInit() {
- final RichTextArea richTextArea = new RichTextArea();
- RootPanel.get().add(richTextArea);
- new Timer() {
- @Override
- public void run() {
+ final RichTextArea richTextArea = new RichTextArea();
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ richTextArea.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
richTextArea.setHTML("<b>foo</b>");
assertEquals("<b>foo</b>", richTextArea.getHTML().toLowerCase());
finishTest();
}
- }.schedule(200);
- delayTestFinish(1000);
+ });
+ RootPanel.get().add(richTextArea);
}
/**
- * Test that an immediate set of HTML is reflected immediately and after a
- * delay. Some platforms have timing subtleties that need to be tested.
+ * Test that an immediate set of HTML is reflected immediately and after the
+ * area loads. Some platforms have timing subtleties that need to be tested.
*/
+ @DoNotRunWith(Platform.Htmlunit)
public void testSetHTMLBeforeInit() {
final RichTextArea richTextArea = new RichTextArea();
- RootPanel.get().add(richTextArea);
- richTextArea.setHTML("<b>foo</b>");
- assertEquals("<b>foo</b>", richTextArea.getHTML().toLowerCase());
- new Timer() {
- @Override
- public void run() {
- assertEquals("<b>foo</b>", richTextArea.getHTML().toLowerCase());
- finishTest();
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ richTextArea.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
+ new Timer() {
+ @Override
+ public void run() {
+ assertEquals("<b>foo</b>", richTextArea.getHTML().toLowerCase());
+ finishTest();
+ }
+ }.schedule(100);
}
- }.schedule(200);
- delayTestFinish(1000);
+ });
+ richTextArea.setHTML("<b>foo</b>");
+ RootPanel.get().add(richTextArea);
+ assertEquals("<b>foo</b>", richTextArea.getHTML().toLowerCase());
}
/**
* Test that delayed set of text is reflected. Some platforms have timing
* subtleties that need to be tested.
*/
+ @DoNotRunWith(Platform.Htmlunit)
public void testSetTextAfterInit() {
final RichTextArea richTextArea = new RichTextArea();
- RootPanel.get().add(richTextArea);
- new Timer() {
- @Override
- public void run() {
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
+ richTextArea.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
richTextArea.setText("foo");
assertEquals("foo", richTextArea.getText());
finishTest();
}
- }.schedule(200);
- delayTestFinish(1000);
+ });
+ RootPanel.get().add(richTextArea);
}
/**
- * Test that an immediate set of text is reflected immediately and after a
- * delay. Some platforms have timing subtleties that need to be tested.
+ * Test that an immediate set of text is reflected immediately and after the
+ * area loads. Some platforms have timing subtleties that need to be tested.
*/
+ @DoNotRunWith(Platform.Htmlunit)
public void testSetTextBeforeInit() {
final RichTextArea richTextArea = new RichTextArea();
- RootPanel.get().add(richTextArea);
richTextArea.setText("foo");
- assertEquals("foo", richTextArea.getText());
- new Timer() {
- @Override
- public void run() {
+ richTextArea.addInitializeHandler(new InitializeHandler() {
+ public void onInitialize(InitializeEvent event) {
assertEquals("foo", richTextArea.getText());
finishTest();
}
- }.schedule(200);
- delayTestFinish(1000);
+ });
+ RootPanel.get().add(richTextArea);
+ assertEquals("foo", richTextArea.getText());
+ delayTestFinish(RICH_TEXT_ASYNC_DELAY);
}
}
diff --git a/user/test/com/google/gwt/user/client/ui/RootPanelTest.java b/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
index 8d985b4..6584fef 100644
--- a/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
@@ -30,6 +30,42 @@
return "com.google.gwt.user.User";
}
+ public void testDetachNowWithErrorOnDetach() {
+ BadWidget w = BadWidget.wrap(createAttachedDivElement());
+ w.setFailOnUnload(true);
+ assertTrue(RootPanel.isInDetachList(w));
+ assertTrue(RootPanel.getBodyElement().isOrHasChild(w.getElement()));
+
+ try {
+ RootPanel.detachNow(w);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(RootPanel.isInDetachList(w));
+ }
+
+ public void testDetachWidgetsWithErrorOnDetach() {
+ BadWidget bad0 = BadWidget.wrap(createAttachedDivElement());
+ bad0.setFailOnUnload(true);
+ BadWidget bad1 = BadWidget.wrap(createAttachedDivElement());
+ bad1.setFailOnUnload(true);
+ assertTrue(RootPanel.isInDetachList(bad0));
+ assertTrue(RootPanel.isInDetachList(bad1));
+ assertTrue(RootPanel.getBodyElement().isOrHasChild(bad0.getElement()));
+ assertTrue(RootPanel.getBodyElement().isOrHasChild(bad1.getElement()));
+
+ try {
+ RootPanel.detachWidgets();
+ fail("Expected AttachDetachException");
+ } catch (AttachDetachException e) {
+ // Expected.
+ assertEquals(2, e.getCauses().size());
+ }
+ assertFalse(RootPanel.isInDetachList(bad0));
+ assertFalse(RootPanel.isInDetachList(bad1));
+ }
+
/**
* Ensures that {@link RootPanel#get(String)} behaves properly.
*/
@@ -63,4 +99,36 @@
RootPanel newARoot = RootPanel.get("a");
assertNotSame("New RootPanel should not be same as old", newARoot, aRoot);
}
+
+ public void testRemoveWithError() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget badWidget = new BadWidget();
+ badWidget.setFailOnUnload(true);
+
+ // Add the widget to a panel.
+ RootPanel.get().add(badWidget);
+ assertTrue(badWidget.isAttached());
+
+ // Remove the widget from the panel.
+ try {
+ RootPanel.get().remove(badWidget);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(badWidget.isAttached());
+ assertNull(badWidget.getParent());
+ assertNull(badWidget.getElement().getParentElement());
+ }
+
+ /**
+ * Create a div and attach it to the {@link RootPanel}.
+ *
+ * @return the new div
+ */
+ private Element createAttachedDivElement() {
+ DivElement elem = Document.get().createDivElement();
+ RootPanel.getBodyElement().appendChild(elem);
+ return elem;
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/ScrollPanelTest.java b/user/test/com/google/gwt/user/client/ui/ScrollPanelTest.java
index 0068b34..be1cd38 100644
--- a/user/test/com/google/gwt/user/client/ui/ScrollPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ScrollPanelTest.java
@@ -15,18 +15,13 @@
*/
package com.google.gwt.user.client.ui;
-import com.google.gwt.junit.client.GWTTestCase;
-
/**
* Tests the ScrollPanel widget.
*/
-public class ScrollPanelTest extends GWTTestCase {
+public class ScrollPanelTest extends SimplePanelTestBase<ScrollPanel> {
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new ScrollPanel());
+ @Override
+ protected ScrollPanel createPanel() {
+ return new ScrollPanel();
}
}
diff --git a/user/test/com/google/gwt/user/client/ui/SimplePanelTestBase.java b/user/test/com/google/gwt/user/client/ui/SimplePanelTestBase.java
new file mode 100644
index 0000000..fe22546
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/SimplePanelTestBase.java
@@ -0,0 +1,30 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+/**
+ * Base tests for classes that extends {@link SimplePanel}.
+ *
+ * @param <T> the panel type
+ */
+public abstract class SimplePanelTestBase<T extends SimplePanel> extends
+ PanelTestBase<T> {
+
+ @Override
+ protected boolean supportsMultipleWidgets() {
+ return false;
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/SplitPanelTest.java b/user/test/com/google/gwt/user/client/ui/SplitPanelTest.java
deleted file mode 100644
index cb627f6..0000000
--- a/user/test/com/google/gwt/user/client/ui/SplitPanelTest.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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 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.junit.client.GWTTestCase;
-import com.google.gwt.user.client.DOM;
-
-/**
- * Tests both {@link HorizontalSplitPanel} and {@link VerticalSplitPanel}.
- */
-public class SplitPanelTest extends GWTTestCase {
-
- private static Widget createMockWidget() {
- final Label label = new Label();
- label.setText("Testing 1, 2, 3");
- DOM.setStyleAttribute(label.getElement(), "fontSize", "72pt");
- return label;
- }
-
- @Override
- public String getModuleName() {
- return "com.google.gwt.user.DebugTest";
- }
-
- public void testHorizontalAttachDetachOrder() {
- HasWidgetsTester.testAll(new HorizontalSplitPanel());
- }
-
- /**
- * Tests creation, widget assignment, null assignment for
- * {@link HorizontalSplitPanel}.
- */
- public void testHorizontalSplitPanelCreate() {
- final HorizontalSplitPanel panel = new HorizontalSplitPanel();
- final Widget widgetA = createMockWidget();
- final Widget widgetB = createMockWidget();
-
- // Intentionally add before setting widgets.
-
- RootPanel.get().add(panel);
-
- panel.setHeight("100px");
- panel.setWidth("100px");
-
- // Ensure position can be set before widgets are added.
- panel.setSplitPosition("20px");
-
- panel.setRightWidget(widgetB);
- panel.setLeftWidget(widgetA);
-
- assertTrue(panel.getRightWidget() == widgetB);
- assertTrue(panel.getLeftWidget() == widgetA);
-
- panel.setLeftWidget(null);
- panel.setRightWidget(null);
-
- assertTrue(panel.getRightWidget() == null);
- assertTrue(panel.getLeftWidget() == null);
-
- panel.setLeftWidget(widgetB);
- panel.setRightWidget(widgetA);
-
- assertTrue(panel.getLeftWidget() == widgetB);
- assertTrue(panel.getRightWidget() == widgetA);
-
- // Ensure we ended up at the right size.
- assertEquals(100, panel.getOffsetWidth());
- assertEquals(100, panel.getOffsetHeight());
- }
-
- public void testDebugId() {
- VerticalSplitPanel vSplit = new VerticalSplitPanel();
- vSplit.ensureDebugId("vsplit");
- Label top = new Label("top");
- vSplit.setTopWidget(top);
- Label bottom = new Label("bottom");
- vSplit.setBottomWidget(bottom);
- UIObjectTest.assertDebugId("vsplit", vSplit.getElement());
- UIObjectTest.assertDebugId("vsplit-top", DOM.getParent(top.getElement()));
- UIObjectTest.assertDebugId("vsplit-bottom", DOM.getParent(bottom.getElement()));
-
- HorizontalSplitPanel hSplit = new HorizontalSplitPanel();
- hSplit.ensureDebugId("hsplit");
- Label left = new Label("left");
- hSplit.setLeftWidget(left);
- Label right = new Label("right");
- hSplit.setRightWidget(right);
- UIObjectTest.assertDebugId("hsplit", hSplit.getElement());
- UIObjectTest.assertDebugId("hsplit-left", DOM.getParent(left.getElement()));
- UIObjectTest.assertDebugId("hsplit-right", DOM.getParent(right.getElement()));
- }
-
- public void testVerticalAttachDetachOrder() {
- HasWidgetsTester.testAll(new VerticalSplitPanel());
- }
-
- /**
- * Tests creation, widget assignment, null assignment for
- * {@link VerticalSplitPanel}.
- */
- public void testVerticalSplitPanelCreate() {
-
- final VerticalSplitPanel panel = new VerticalSplitPanel();
- final Widget widgetA = createMockWidget();
- final Widget widgetB = createMockWidget();
-
- // Intentionally add before setting widgets.
- RootPanel.get().add(panel);
-
- panel.setHeight("100px");
- panel.setWidth("100px");
- // Ensure position can be set before widgets are added.
- panel.setSplitPosition("20px");
-
- panel.setBottomWidget(widgetB);
- panel.setTopWidget(widgetA);
-
- assertTrue(panel.getBottomWidget() == widgetB);
- assertTrue(panel.getTopWidget() == widgetA);
-
- panel.setTopWidget(null);
- panel.setBottomWidget(null);
-
- assertTrue(panel.getTopWidget() == null);
- assertTrue(panel.getBottomWidget() == null);
-
- panel.setTopWidget(widgetB);
- panel.setBottomWidget(widgetA);
-
- assertTrue(panel.getTopWidget() == widgetB);
- assertTrue(panel.getBottomWidget() == widgetA);
-
- // Ensure we ended up at the right size.
- assertEquals(100, panel.getOffsetWidth());
- assertEquals(100, panel.getOffsetHeight());
- }
-}
diff --git a/user/test/com/google/gwt/user/client/ui/SplitPanelTestBase.java b/user/test/com/google/gwt/user/client/ui/SplitPanelTestBase.java
new file mode 100644
index 0000000..9298e3b
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/SplitPanelTestBase.java
@@ -0,0 +1,117 @@
+/*
+ * 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
+ * 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;
+
+/**
+ * Tests both {@link HorizontalSplitPanel} and {@link VerticalSplitPanel}.
+ *
+ * @param <T> the panel type
+ */
+public abstract class SplitPanelTestBase<T extends SplitPanel> extends
+ PanelTestBase<T> {
+
+ private static Widget createMockWidget() {
+ final Label label = new Label();
+ label.setText("Testing 1, 2, 3");
+ DOM.setStyleAttribute(label.getElement(), "fontSize", "72pt");
+ return label;
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.DebugTest";
+ }
+
+ @Override
+ public void testAttachDetachOrder() {
+ HasWidgetsTester.testAll(createPanel(),
+ new HasWidgetsTester.DefaultWidgetAdder(), false);
+ }
+
+ /**
+ * Tests creation, widget assignment, null assignment.
+ */
+ public void testSplitPanelCreate() {
+ final T panel = createPanel();
+ final Widget widgetA = createMockWidget();
+ final Widget widgetB = createMockWidget();
+
+ // Intentionally add before setting widgets.
+ RootPanel.get().add(panel);
+
+ panel.setHeight("100px");
+ panel.setWidth("100px");
+
+ // Ensure position can be set before widgets are added.
+ panel.setSplitPosition("20px");
+
+ setEndOfLineWidget(panel, widgetB);
+ setStartOfLineWidget(panel, widgetA);
+
+ assertTrue(getEndOfLineWidget(panel) == widgetB);
+ assertTrue(getStartOfLineWidget(panel) == widgetA);
+
+ setStartOfLineWidget(panel, null);
+ setEndOfLineWidget(panel, null);
+
+ assertTrue(getStartOfLineWidget(panel) == null);
+ assertTrue(getEndOfLineWidget(panel) == null);
+
+ setStartOfLineWidget(panel, widgetB);
+ setEndOfLineWidget(panel, widgetA);
+
+ assertTrue(getStartOfLineWidget(panel) == widgetB);
+ assertTrue(getEndOfLineWidget(panel) == widgetA);
+
+ // Ensure we ended up at the right size.
+ assertEquals(100, panel.getOffsetWidth());
+ assertEquals(100, panel.getOffsetHeight());
+ }
+
+ /**
+ * Get the widget at the end of the line.
+ *
+ * @param split the {@link SplitPanel}
+ * @return the widget
+ */
+ protected abstract Widget getEndOfLineWidget(T split);
+
+ /**
+ * Get the widget at the start of the line.
+ *
+ * @param split the {@link SplitPanel}
+ * @return the widget
+ */
+ protected abstract Widget getStartOfLineWidget(T split);
+
+ /**
+ * Set the widget at the end of the line.
+ *
+ * @param split the {@link SplitPanel}
+ * @param w the widget to set
+ */
+ protected abstract void setEndOfLineWidget(T split, Widget w);
+
+ /**
+ * Set the widget at the start of the line.
+ *
+ * @param split the {@link SplitPanel}
+ * @param w the widget to set
+ */
+ protected abstract void setStartOfLineWidget(T split, Widget w);
+}
diff --git a/user/test/com/google/gwt/user/client/ui/StackPanelTest.java b/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
index 635fede..0e6ab61 100644
--- a/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
@@ -15,7 +15,6 @@
*/
package com.google.gwt.user.client.ui;
-import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
@@ -24,7 +23,7 @@
/**
* Test cases for {@link StackPanel}.
*/
-public class StackPanelTest extends GWTTestCase {
+public class StackPanelTest extends PanelTestBase<StackPanel> {
static class Adder implements HasWidgetsTester.WidgetAdder {
public void addChild(HasWidgets container, Widget child) {
@@ -50,8 +49,9 @@
return accum;
}
+ @Override
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(createStackPanel(), new Adder());
+ HasWidgetsTester.testAll(createStackPanel(), new Adder(), true);
}
public void testDebugId() {
@@ -162,4 +162,9 @@
protected StackPanel createStackPanel() {
return new StackPanel();
}
+
+ @Override
+ protected StackPanel createPanel() {
+ return createStackPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/TabPanelTest.java b/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
index a3a8bbd..75c634e 100644
--- a/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
@@ -59,7 +59,7 @@
}
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(createTabPanel(), new Adder());
+ HasWidgetsTester.testAll(createTabPanel(), new Adder(), true);
}
public void testDebugId() {
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 f492a60..8e037d8 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
@@ -38,4 +38,35 @@
item.setWidget(null);
assertEquals("Test", widget.getText());
}
+
+ public void testSetWidgetNullWithError() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget badWidget = new BadWidget();
+ badWidget.setFailOnUnload(true);
+
+ // Add the widget to a panel.
+ TreeItem item = new TreeItem(badWidget);
+ assertFalse(badWidget.isAttached());
+
+ // Attach the widget.
+ Tree tree = new Tree();
+ tree.addItem(item);
+ RootPanel.get().add(tree);
+ assertTrue(badWidget.isAttached());
+
+ // Remove the widget from the panel.
+ try {
+ item.setWidget(null);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(badWidget.isAttached());
+ assertNull(badWidget.getParent());
+ assertNull(badWidget.getElement().getParentElement());
+ assertNull(item.getWidget());
+
+ // Detach the panel.
+ RootPanel.get().remove(tree);
+ }
}
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 5912ed8..07f8261 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeTest.java
@@ -40,7 +40,7 @@
}
public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new Tree(), new Adder());
+ HasWidgetsTester.testAll(new Tree(), new Adder(), true);
}
public void testClear() {
diff --git a/user/test/com/google/gwt/user/client/ui/VerticalPanelTest.java b/user/test/com/google/gwt/user/client/ui/VerticalPanelTest.java
index 84193fd..4fb7019 100644
--- a/user/test/com/google/gwt/user/client/ui/VerticalPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/VerticalPanelTest.java
@@ -22,11 +22,7 @@
/**
* A test for {@link VerticalPanel}.
*/
-public class VerticalPanelTest extends AbstractCellPanelTest {
-
- public void testAttachDetachOrder() {
- HasWidgetsTester.testAll(new VerticalPanel());
- }
+public class VerticalPanelTest extends AbstractCellPanelTest<VerticalPanel> {
public void testDebugId() {
VerticalPanel p = new VerticalPanel();
@@ -91,4 +87,9 @@
p.add(new Label("c"));
return p;
}
+
+ @Override
+ protected VerticalPanel createPanel() {
+ return new VerticalPanel();
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/VerticalSplitPanelTest.java b/user/test/com/google/gwt/user/client/ui/VerticalSplitPanelTest.java
new file mode 100644
index 0000000..41f24d6
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/VerticalSplitPanelTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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
+ * 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;
+
+/**
+ * Tests for {@link VerticalSplitPanel}.
+ */
+public class VerticalSplitPanelTest extends
+ SplitPanelTestBase<VerticalSplitPanel> {
+
+ public void testDebugId() {
+ VerticalSplitPanel vSplit = new VerticalSplitPanel();
+ vSplit.ensureDebugId("vsplit");
+ Label top = new Label("top");
+ vSplit.setTopWidget(top);
+ Label bottom = new Label("bottom");
+ vSplit.setBottomWidget(bottom);
+ UIObjectTest.assertDebugId("vsplit", vSplit.getElement());
+ UIObjectTest.assertDebugId("vsplit-top", DOM.getParent(top.getElement()));
+ UIObjectTest.assertDebugId("vsplit-bottom",
+ DOM.getParent(bottom.getElement()));
+ }
+
+ @Override
+ protected VerticalSplitPanel createPanel() {
+ return new VerticalSplitPanel();
+ }
+
+ @Override
+ protected Widget getEndOfLineWidget(VerticalSplitPanel split) {
+ return split.getBottomWidget();
+ }
+
+ @Override
+ protected Widget getStartOfLineWidget(VerticalSplitPanel split) {
+ return split.getTopWidget();
+ }
+
+ @Override
+ protected void setEndOfLineWidget(VerticalSplitPanel split, Widget w) {
+ split.setBottomWidget(w);
+ }
+
+ @Override
+ protected void setStartOfLineWidget(VerticalSplitPanel split, Widget w) {
+ split.setTopWidget(w);
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/WidgetTest.java b/user/test/com/google/gwt/user/client/ui/WidgetTest.java
index 109ce8e..88f0036 100644
--- a/user/test/com/google/gwt/user/client/ui/WidgetTest.java
+++ b/user/test/com/google/gwt/user/client/ui/WidgetTest.java
@@ -58,4 +58,118 @@
assertEquals(0, a.getHandlerCount(ClickEvent.getType()));
}
+ public void testOnAttachWithErrorDoAttachChildren() {
+ // Create a panel that will throw an exception doAttachChildren
+ BadWidget w = new BadWidget();
+ w.setFailAttachChildren(true);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+
+ // Attach the widget.
+ try {
+ RootPanel.get().add(w);
+ fail("Expected AttachDetachException");
+ } catch (AttachDetachException e) {
+ // Expected.
+ }
+ assertTrue(w.isAttached());
+ assertEquals(RootPanel.get(), w.getParent());
+
+ // Detach the widget.
+ RootPanel.get().remove(w);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+ }
+
+ public void testOnAttachWithErrorOnLoad() {
+ // Create a widget that will throw an exception onLoad.
+ BadWidget w = new BadWidget();
+ w.setFailOnLoad(true);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+
+ // Attach the widget.
+ try {
+ RootPanel.get().add(w);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertTrue(w.isAttached());
+ assertEquals(RootPanel.get(), w.getParent());
+
+ // Detach the widget.
+ RootPanel.get().remove(w);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+ }
+
+ public void testOnDetachWithErrorDoDetachChildren() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget w = new BadWidget();
+ w.setFailDetachChildren(true);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+
+ // Attach the widget.
+ RootPanel.get().add(w);
+ assertTrue(w.isAttached());
+ assertEquals(RootPanel.get(), w.getParent());
+
+ // Detach the widget.
+ try {
+ RootPanel.get().remove(w);
+ fail("Expected AttachDetachException");
+ } catch (AttachDetachException e) {
+ // Expected.
+ }
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+ }
+
+ public void testOnDetachWithErrorOnUnload() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget w = new BadWidget();
+ w.setFailOnUnload(true);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+
+ // Attach the widget.
+ RootPanel.get().add(w);
+ assertTrue(w.isAttached());
+ assertEquals(RootPanel.get(), w.getParent());
+
+ // Detach the widget.
+ try {
+ RootPanel.get().remove(w);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+ }
+
+ public void testSetParentWithErrorOnUnload() {
+ // Create a widget that will throw an exception onUnload.
+ BadWidget w = new BadWidget();
+ w.setFailOnUnload(true);
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+
+ // Attach the widget.
+ RootPanel.get().add(w);
+ assertTrue(w.isAttached());
+ assertEquals(RootPanel.get(), w.getParent());
+
+ // Detach the widget.
+ try {
+ w.removeFromParent();
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ assertFalse(w.isAttached());
+ assertNull(w.getParent());
+ }
}
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/AbstractSerializableTypes.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/AbstractSerializableTypes.java
index 8eec178..3669e3b 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/AbstractSerializableTypes.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/AbstractSerializableTypes.java
@@ -24,6 +24,7 @@
*
* TODO(mmendez): check that the warnings are emitted.
*/
+@SuppressWarnings("rpc-validation")
public interface AbstractSerializableTypes extends RemoteService {
/**
* Regular interface.
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ManualSerialization.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ManualSerialization.java
index 09c8e4b..2d0db57 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ManualSerialization.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ManualSerialization.java
@@ -26,6 +26,7 @@
* will not fail if a manually serialized type has a field that is not
* serializables.
*/
+@SuppressWarnings("rpc-validation")
public interface ManualSerialization extends RemoteService {
/**
* Manually serialized. Field b is not serializable.
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/MissingGwtTypeArgs.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/MissingGwtTypeArgs.java
index 3330d58..1aa2d7b 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/MissingGwtTypeArgs.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/MissingGwtTypeArgs.java
@@ -25,6 +25,7 @@
* will pull in the all of the serializable subtypes of Object if an unparameterized
* type is used.
*/
+@SuppressWarnings("rpc-validation")
public interface MissingGwtTypeArgs extends RemoteService {
List getList();
}
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/NoSerializableTypes.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/NoSerializableTypes.java
index 4731b65..f1cebde 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/NoSerializableTypes.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/NoSerializableTypes.java
@@ -22,6 +22,7 @@
* {@link com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder SerializableTypeOracleBuilder}
* will fail if a root type is not serializable.
*/
+@SuppressWarnings("rpc-validation")
public interface NoSerializableTypes extends RemoteService {
/**
*
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectArrayInMethodSignature.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectArrayInMethodSignature.java
index 97e29ff..376827f 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectArrayInMethodSignature.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectArrayInMethodSignature.java
@@ -22,6 +22,7 @@
* {@link com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder SerializableTypeOracleBuilder}
* will not fail if Object[] is used in a method signature.
*/
+@SuppressWarnings("rpc-validation")
public interface ObjectArrayInMethodSignature extends RemoteService {
Object[] getObjects();
}
diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectInMethodSignature.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectInMethodSignature.java
index c99c705..10dbfb7 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectInMethodSignature.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/ObjectInMethodSignature.java
@@ -23,6 +23,7 @@
* {@link com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder SerializableTypeOracleBuilder}
* will not fail if Object is used in a method signature.
*/
+@SuppressWarnings("rpc-validation")
public interface ObjectInMethodSignature extends RemoteService {
Object getObject();
}
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
index 3089f9a..f59efd6 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -55,6 +55,7 @@
C c();
}
+ @SuppressWarnings("rpc-validation")
private static interface A extends RemoteService {
void method1() throws SerializableException;