/*
* Copyright 2010 bufferings[at]gmail.com
*
* 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 bufferings.ktr.wjr.client;
import static bufferings.ktr.wjr.client.ui.widget.JQueryUI.*;
import static bufferings.ktr.wjr.shared.util.Preconditions.*;
import java.util.ArrayList;
import java.util.List;
import bufferings.ktr.wjr.client.ui.WjrButtonPanel;
import bufferings.ktr.wjr.client.ui.WjrDialogPanel;
import bufferings.ktr.wjr.client.ui.WjrPopupPanel;
import bufferings.ktr.wjr.client.ui.WjrResultPanel;
import bufferings.ktr.wjr.client.ui.WjrTracePanel;
import bufferings.ktr.wjr.client.ui.WjrTreePanel;
import bufferings.ktr.wjr.client.ui.widget.WjrTree;
import bufferings.ktr.wjr.client.ui.widget.WjrTreeItem;
import bufferings.ktr.wjr.shared.model.WjrClassItem;
import bufferings.ktr.wjr.shared.model.WjrConfig;
import bufferings.ktr.wjr.shared.model.WjrMethodItem;
import bufferings.ktr.wjr.shared.model.WjrStore;
import bufferings.ktr.wjr.shared.model.WjrStoreItem;
import bufferings.ktr.wjr.shared.model.WjrStoreItem.State;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
/**
* The view of the Kotori Web JUnit Runner.
*
* This class controls all the view components of KtrWjr.
*
* @author bufferings[at]gmail.com
*/
public class WjrView extends Composite implements WjrDisplay,
SelectionHandler<WjrTreeItem>, ValueChangeHandler<WjrTreeItem> {
private static final String LOADING_POPUP_TEXT = "Loading...";
private static final String RUNNING_POPUP_TEXT = "Running...";
private static final String CANCELING_POPUP_TEXT = "Canceling...";
private static WjrViewUiBinder uiBinder = GWT.create(WjrViewUiBinder.class);
interface WjrViewUiBinder extends UiBinder<Widget, WjrView> {
}
/**
* The view event handler.
*/
protected WjrDisplayHandler handler;
/**
* The container widget of this view.
*/
protected HasWidgets container;
/**
* The result panel which shows the result summary of the tests.
*/
@UiField
protected WjrResultPanel resultPanel;
/**
* The tree panel which shows the test cases and some icon buttons.
*/
@UiField
protected WjrTreePanel treePanel;
/**
* The button panel of the run and the cancel button.
*/
@UiField
protected WjrButtonPanel buttonPanel;
/**
* The trace panel which shows the trace and the log.
*/
@UiField
protected WjrTracePanel tracePanel;
/**
* The popup panel which is shown while the process is running.
*/
protected WjrPopupPanel popup;
/**
* The dialog panel which shows the error message.
*/
protected WjrDialogPanel dialog;
/**
* The tree items to associate with the store items.
*/
protected List<WjrTreeItem> treeItems = new ArrayList<WjrTreeItem>();
/**
* The store items to associate with the tree items. The store items is read
* only.
*/
protected List<WjrStoreItem> storeItems = new ArrayList<WjrStoreItem>();
/**
* Whether loading the store or not.
*/
protected boolean loading = false;
/**
* Whether running the tests or not.
*/
protected boolean running = false;
/**
* Whether canceled running the tests or not.
*/
protected boolean canceled = false;
/**
* The configuration.
*/
protected WjrConfig config = new WjrConfig();
/**
* UiFactory method which creates WjrButtonPanel and handles its events.
*
* @return The instance of the WjrButtonPanel.
*/
@UiFactory
protected WjrButtonPanel createWjrButtonPanel() {
return new WjrButtonPanel(new WjrButtonPanel.Handler() {
public void onButtonClicked(ClickEvent evt) {
if (!running) {
startRunning();
} else {
cancelRunning();
}
}
});
}
/**
* UiFactory method which creates WjrTreePanel and handles its events.
*
* @return The instance of the WjrTreePanel.
*/
@UiFactory
protected WjrTreePanel createWjrTreePanel() {
return new WjrTreePanel(new WjrTreePanel.Handler() {
public void onCheckAllButtonClicked(ClickEvent event) {
updateRunButtonDisabled();
}
public void onClearButtonClicked(ClickEvent event) {
handler.onClearButtonClick();
}
public void onReloadButtonClicked(ClickEvent event) {
startReloading();
}
public void onUncheckAllButtonClicked(ClickEvent event) {
updateRunButtonDisabled();
}
});
}
/**
* {@inheritDoc}
*/
public void go(WjrDisplayHandler handler, HasWidgets container) {
checkNotNull(handler, "The handler parameter is null.");
checkNotNull(container, "The container parameter is null.");
this.handler = handler;
this.container = container;
dialog = new WjrDialogPanel("Kotori Web JUnit Runner");
popup = new WjrPopupPanel();
}
/**
* {@inheritDoc}
*/
public void notifyLoadingConfigSucceeded(WjrConfig config) {
finishLoadingConfig(config);
startReloading();
}
/**
* {@inheritDoc}
*/
public void notifyLoadingConfigFailed(WjrConfig config, Throwable caught) {
dialog.show("Cannot load the tests.", caught);
finishLoadingConfig(config);
}
/**
* Performs the post-processing of loading configuration.
*
* Shows the KtrWjr panels.
*
* @param config
* The user configuration.
*/
private void finishLoadingConfig(WjrConfig config) {
initWidget(uiBinder.createAndBindUi(this));
applyConfig(config);
setData(new WjrStore());
updateRunButtonDisabled();
updateTreeButtonsDisabled();
container.add(this);
}
/**
* Sets the configuration.
*
* @param newConfig
* The configuration
*/
private void applyConfig(WjrConfig newConfig) {
config = newConfig;
tracePanel.setTraceTabVisible(config.isLogHookEnabled());
}
/**
* {@inheritDoc}
*/
public void notifyLoadingStoreSucceeded() {
finishReloading();
}
/**
* {@inheritDoc}
*/
public void notifyLoadingStoreFailed(Throwable caught) {
dialog.show("Cannot load the tests.", caught);
finishReloading();
}
/**
* {@inheritDoc}
*/
public void notifyRunningFinished() {
if (running) {
running = false;
canceled = false;
buttonPanel.changeToRunButton();
popup.hide();
updateRunButtonDisabled();
updateTreeButtonsDisabled();
}
}
/**
* {@inheritDoc}
*/
public void setData(WjrStore store) {
treeItems.clear();
storeItems.clear();
updateTracePanel(null);
updateResultPanel(store);
WjrTree tree = treePanel.getTree();
tree.removeItems();
List<WjrClassItem> classStoreItems = store.getClassItems();
for (WjrClassItem classStoreItem : classStoreItems) {
WjrTreeItem classTreeItem = createTreeItem(classStoreItem);
storeItems.add(classStoreItem);
treeItems.add(classTreeItem);
List<WjrMethodItem> methodStoreItems =
store.getMethodItems(classStoreItem.getClassName());
storeItems.addAll(methodStoreItems);
for (WjrMethodItem wjrMethodItem : methodStoreItems) {
WjrTreeItem methodTreeItem = createTreeItem(wjrMethodItem);
treeItems.add(methodTreeItem);
classTreeItem.addItem(methodTreeItem);
}
tree.addItem(classTreeItem);
}
}
/**
* {@inheritDoc}
*/
public List<WjrMethodItem> getCheckedMethodItems() {
List<WjrMethodItem> ret = new ArrayList<WjrMethodItem>();
for (int i = 0, n = treeItems.size(); i < n; i++) {
if (treeItems.get(i).isChecked()) {
WjrStoreItem storeItem = storeItems.get(i);
if (storeItem instanceof WjrMethodItem) {
ret.add((WjrMethodItem) storeItem);
}
}
}
return ret;
}
/**
* {@inheritDoc}
*/
public void repaintAllTreeItems(WjrStore store) {
updateResultPanel(store);
for (int i = 0, n = treeItems.size(); i < n; i++) {
repaintTreeItem(treeItems.get(i), storeItems.get(i));
}
}
/**
* {@inheritDoc}
*/
public void repaintTreeItemAncestors(WjrStore store, WjrMethodItem methodItem) {
updateResultPanel(store);
WjrTreeItem treeItem = treeItems.get(storeItems.indexOf(methodItem));
repaintTreeItem(treeItem, methodItem);
WjrTreeItem parentTreeItem = treeItem.getParentItem();
repaintTreeItem(parentTreeItem, storeItems.get(treeItems
.indexOf(parentTreeItem)));
}
/**
* {@inheritDoc}
*/
public void onSelection(SelectionEvent<WjrTreeItem> event) {
WjrTreeItem treeItem = event.getSelectedItem();
if (!treeItem.isSelected()) {
treeItem.setSelectedStyle("");
updateTracePanel(null);
} else {
WjrStoreItem storeItem =
(WjrStoreItem) storeItems.get(treeItems.indexOf(treeItem));
treeItem.setSelectedStyle(getTreeItemSelectedStyle(storeItem));
updateTracePanel(storeItem);
}
}
/**
* {@inheritDoc}
*/
public void onValueChange(ValueChangeEvent<WjrTreeItem> event) {
updateRunButtonDisabled();
}
/**
* Starts reloading the test store.
*/
private void startReloading() {
if (!loading) {
loading = true;
setData(new WjrStore());
updateRunButtonDisabled();
updateTreeButtonsDisabled();
popup.setText(LOADING_POPUP_TEXT);
popup.show();
handler.onLoadStore();
}
}
/**
* Performs the post-processing of reloading.
*/
private void finishReloading() {
if (loading) {
loading = false;
updateRunButtonDisabled();
updateTreeButtonsDisabled();
popup.hide();
}
}
/**
* Starts running the test methods.
*/
private void startRunning() {
if (!running) {
running = true;
canceled = false;
buttonPanel.changeToCancelButton();
updateTreeButtonsDisabled();
popup.setText(RUNNING_POPUP_TEXT);
popup.show();
handler.onRunButtonClick();
}
}
/**
* Cancel running the test methods.
*/
private void cancelRunning() {
if (running && !canceled) {
canceled = true;
updateRunButtonDisabled();
popup.setText(CANCELING_POPUP_TEXT);
handler.onCancelButtonClick();
}
}
/**
* Updates the tree panel buttons' disabled.
*/
private void updateTreeButtonsDisabled() {
if (treeItems.size() == 0) {
treePanel.setCheckAllButtonDisabled(true);
treePanel.setUncheckAllButtonDisabled(true);
treePanel.setExpandAllButtonDisabled(true);
treePanel.setCollapseAllButtonDisabled(true);
treePanel.setClearButtonDisabled(true);
if (loading) {
treePanel.setReloadButtonDisabled(true);
} else {
treePanel.setReloadButtonDisabled(false);
}
} else {
treePanel.setCheckAllButtonDisabled(false);
treePanel.setUncheckAllButtonDisabled(false);
treePanel.setExpandAllButtonDisabled(false);
treePanel.setCollapseAllButtonDisabled(false);
if (running) {
treePanel.setClearButtonDisabled(true);
treePanel.setReloadButtonDisabled(true);
} else {
treePanel.setClearButtonDisabled(false);
treePanel.setReloadButtonDisabled(false);
}
}
}
/**
* Create the tree item from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item.
*/
private WjrTreeItem createTreeItem(WjrStoreItem storeItem) {
WjrTreeItem treeItem = new WjrTreeItem();
treeItem.addSelectionHandler(this);
treeItem.addValueChangeHandler(this);
repaintTreeItem(treeItem, storeItem);
return treeItem;
}
/**
* Updates the result panel from the test store.
*
* @param store
* The test store.
*/
private void updateResultPanel(WjrStore store) {
int runningsCount = store.getRunningCount() + store.getRetryWaitingCount();
int runsCount =
store.getTotalCount() - store.getNotYetCount() - runningsCount;
resultPanel.updateResults(
runningsCount,
runsCount,
store.getTotalCount(),
store.getErrorCount(),
store.getFailureCount());
}
/**
* Updates the trace panel with the selected tree item.
*
* @param selectedItem
* The selected tree item. If no item is selected, set null.
*/
private void updateTracePanel(WjrStoreItem selectedItem) {
tracePanel.setTrace(getTreeItemTrace(selectedItem));
if (config.isLogHookEnabled()) {
tracePanel.setLog(getTreeItemLog(selectedItem));
}
}
/**
* Repaints the tree item by the store item.
*
* @param treeItem
* The tree item.
* @param storeItem
* The test store item.
*/
private void repaintTreeItem(WjrTreeItem treeItem, WjrStoreItem storeItem) {
treeItem.setText(getTreeItemText(storeItem));
treeItem.setIcon(getTreeItemIcon(storeItem));
if (treeItem.isSelected()) {
treeItem.setSelectedStyle(getTreeItemSelectedStyle(storeItem));
updateTracePanel(storeItem);
}
}
/**
* Updates the run (or cancel) button disabled.
*/
private void updateRunButtonDisabled() {
if (loading) {
buttonPanel.setButtonDisabled(true);
return;
}
if (running) {
buttonPanel.setButtonDisabled(canceled);
return;
}
boolean checked = false;
for (WjrTreeItem treeItem : treeItems) {
if (treeItem.isChecked()) {
checked = true;
break;
}
}
buttonPanel.setButtonDisabled(!checked);
}
/**
* Gets the tree item icon from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item icon css class of JQueryUI.
*/
private String getTreeItemIcon(WjrStoreItem storeItem) {
switch (storeItem.getState()) {
case SUCCESS:
return UI_ICON_CHECK;
case FAILURE:
return UI_ICON_NOTICE;
case ERROR:
return UI_ICON_CLOSE;
case NOT_YET:
return UI_ICON_MINUS;
case RUNNING:
return UI_ICON_ARRORREFRESH_1_W;
case RETRY_WAITING:
return UI_ICON_CLOCK;
default:
// GWT does not emulate format, so i use the format method of Guice's
// Preconditions class.
throw new AssertionError(format("Unknown state. [State=%s]", storeItem
.getState()
.name()));
}
}
/**
* Gets the tree item selected style from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item selected css class name of JQueryUI.
*/
private String getTreeItemSelectedStyle(WjrStoreItem storeItem) {
switch (storeItem.getState()) {
case SUCCESS:
case NOT_YET:
case RUNNING:
case RETRY_WAITING:
return UI_STATE_HIGHLIGHT;
case FAILURE:
case ERROR:
return UI_STATE_ERROR;
default:
// GWT does not emulate format, so i use the format method of Guice's
// Preconditions class.
throw new AssertionError(format("Unknown state. [State=%s]", storeItem
.getState()
.name()));
}
}
/**
* Gets the tree item text from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item text.
*/
private String getTreeItemText(WjrStoreItem storeItem) {
if (storeItem instanceof WjrClassItem) {
return getTreeItemTextFromClassItem((WjrClassItem) storeItem);
} else {
return getTreeItemTextFromMethodItem((WjrMethodItem) storeItem);
}
}
/**
* Gets the tree item text from the test class item.
*
* @param classItem
* The test class item.
* @return The tree item text.
*/
private String getTreeItemTextFromClassItem(WjrClassItem classItem) {
return classItem.getClassName();
}
/**
* Gets the tree item text from the test method item.
*
* @param methodItem
* The test method item.
* @return The tree item text.
*/
private String getTreeItemTextFromMethodItem(WjrMethodItem methodItem) {
StringBuilder sb = new StringBuilder(methodItem.getMethodName());
if (methodItem.getState() == State.RETRY_WAITING) {
sb.append(" (").append(getRetryWaitingString(methodItem)).append(")");
} else if (methodItem.getState() != State.NOT_YET
&& methodItem.getState() != State.RUNNING) {
sb.append(" (").append(getTimeString(methodItem)).append(")");
}
return sb.toString();
}
/**
* Gets the time string from the test method item.
*
* @param methodItem
* The test method item.
* @return The time string.
*/
private String getTimeString(WjrMethodItem methodItem) {
StringBuilder sb = new StringBuilder();
String time = methodItem.getTime();
sb.append(time.length() > 0 ? time : "-").append("ms");
if (config.isCpumsEnabled()) {
String cpuTime = methodItem.getCpuTime();
sb.append(" ");
sb.append(cpuTime.length() > 0 ? cpuTime : "-").append("cpu_ms");
}
if (config.isApimsEnabled()) {
String apiTime = methodItem.getApiTime();
sb.append(" ");
sb.append(apiTime.length() > 0 ? apiTime : "-").append("api_ms");
}
return sb.toString();
}
/**
* Gets the retry waiting string from the test method item.
*
* @param methodItem
* The test method item.
* @return The time string.
*/
private Object getRetryWaitingString(WjrMethodItem methodItem) {
StringBuilder sb = new StringBuilder();
sb.append("waiting:");
sb.append(methodItem.getWaitingSeconds());
sb.append("s retry count:");
sb.append(methodItem.getRetryCount());
sb.append("/");
sb.append(methodItem.getMaxRetryCount());
return sb.toString();
}
/**
* Gets the tree item log from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item log.
*/
private String getTreeItemLog(WjrStoreItem storeItem) {
if (storeItem == null || storeItem instanceof WjrClassItem) {
return null;
} else {
WjrMethodItem methodItem = (WjrMethodItem) storeItem;
if (methodItem.getState() == State.NOT_YET
|| methodItem.getState() == State.RUNNING
|| methodItem.getState() == State.RETRY_WAITING) {
return null;
} else {
String log = methodItem.getLog();
return getTimeString(methodItem) + (log != null ? "\n" + log : "");
}
}
}
/**
* Gets the tree item trace from the test store item.
*
* @param storeItem
* The test store item.
* @return The tree item trace. If the storeItem is null or the trace is not
* set, returns null.
*/
private String getTreeItemTrace(WjrStoreItem storeItem) {
if (storeItem == null || storeItem instanceof WjrClassItem) {
return null;
} else {
WjrMethodItem methodItem = (WjrMethodItem) storeItem;
if (methodItem.getState() == State.NOT_YET
|| methodItem.getState() == State.RUNNING
|| methodItem.getState() == State.RETRY_WAITING) {
return null;
} else {
String trace = methodItem.getTrace();
return (trace != null ? trace : "");
}
}
}
}