diff options
author | Henrik Paul <henrik@vaadin.com> | 2015-02-03 14:02:20 +0200 |
---|---|---|
committer | Henrik Paul <henrik@vaadin.com> | 2015-02-03 14:02:41 +0200 |
commit | 0dcaa660c5bd2c5c2bdea352c5141754a549b18c (patch) | |
tree | 2e698a3fb2dce442c292505d4fbc94d676f3193b | |
parent | 0533c6d9c88f78dc43e5c78f18ed84d1aa249526 (diff) | |
parent | a508ed7b4aa062334ad84d7967cb2bdd5d8ecc26 (diff) | |
download | vaadin-framework-0dcaa660c5bd2c5c2bdea352c5141754a549b18c.tar.gz vaadin-framework-0dcaa660c5bd2c5c2bdea352c5141754a549b18c.zip |
Merge branch 'master' into grid
Change-Id: If5f2f438715da97592a61cad7fd292554d8c5ea9
64 files changed, 3524 insertions, 662 deletions
diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 2e76434709..50c7169225 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -54,15 +54,17 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default; // Selected .#{$primary-stylename}-row-selected { + $grid-sel-bg: $v-grid-row-selected-background-color; + > .#{$primary-stylename}-cell { - @include valo-gradient($v-selection-color); - color: valo-font-color($v-selection-color); - text-shadow: valo-text-shadow($font-color: valo-font-color($v-selection-color), $background-color: $v-selection-color); - border-color: adjust-color($v-selection-color, $lightness: -8%, $saturation: -8%); + @include valo-gradient($grid-sel-bg); + color: valo-font-color($grid-sel-bg); + text-shadow: valo-text-shadow($font-color: valo-font-color($grid-sel-bg), $background-color: $grid-sel-bg); + border-color: adjust-color($grid-sel-bg, $lightness: -8%, $saturation: -8%); } > .#{$primary-stylename}-cell-focused:before { - border-color: adjust-color($v-selection-color, $lightness: 20%); + border-color: adjust-color($grid-sel-bg, $lightness: 20%); } } diff --git a/WebContent/VAADIN/themes/valo/components/_orderedlayout.scss b/WebContent/VAADIN/themes/valo/components/_orderedlayout.scss index fb40763ade..8a7c877d69 100644 --- a/WebContent/VAADIN/themes/valo/components/_orderedlayout.scss +++ b/WebContent/VAADIN/themes/valo/components/_orderedlayout.scss @@ -43,6 +43,11 @@ .v-horizontal > .v-expand > .v-slot { height: 100%; } + + /* Workaround for IE8+IE9 bug where clicking inside an input area which is inside a div with negative margin causes cursor position to jump to wrong position. See #11152 */ + .v-horizontal > .v-expand > .v-slot { + position: relative; + } .v-vertical > .v-spacing, .v-vertical > .v-expand > .v-spacing { diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html index 8e6385684f..6cedeb1e21 100644 --- a/WebContent/release-notes.html +++ b/WebContent/release-notes.html @@ -137,6 +137,8 @@ <li>The semantics of empty and required for Field classes has been made more consistent. This mainly affects Checkbox which is now considered to be empty when it is not checked.</li> <li>The previously inconsistent behavior in HTML vs plain text rendering of Calendar event captions has been made consistent.</li> <li>Support for Opera 12 has been dropped. Newer versions based on the Blink rendering engine are still supported.</li> + <li>Window's accessibility shortcut was moved to server-side. Now setCloseShortcut overrides the default value, while addCloseShortcut can be used to add more than one shortcut key for closing the window. + The protected value closeShortcut in Window was removed.</li> </ul> <h3 id="knownissues">Known issues</h3> <ul> diff --git a/build.properties b/build.properties index 286713b616..53196da206 100644 --- a/build.properties +++ b/build.properties @@ -5,6 +5,6 @@ vaadin.vendor=Vaadin Ltd vaadin.url=http://vaadin.com vaadin.java.version=1.6 vaadin.version=0.0.0.unversioned-development-build -vaadin.sass.version=0.9.10 +vaadin.sass.version=0.9.12 gwt.version=2.7.0.vaadin1 commons-io.version=2.4 diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 0ad1631e19..bd676e4ee9 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -362,7 +362,16 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { return indexToRowMap.get(Integer.valueOf(rowIndex)); } - @Override + /** + * Retrieves the index for given row object. + * <p> + * <em>Note:</em> This method does not verify that the given row object + * exists at all in this DataSource. + * + * @param row + * the row object + * @return index of the row; or <code>-1</code> if row is not available + */ public int indexOf(T row) { Object key = getRowKey(row); if (keyToIndexMap.containsKey(key)) { diff --git a/client/src/com/vaadin/client/data/DataSource.java b/client/src/com/vaadin/client/data/DataSource.java index 076226bf5c..f3711c3c68 100644 --- a/client/src/com/vaadin/client/data/DataSource.java +++ b/client/src/com/vaadin/client/data/DataSource.java @@ -194,16 +194,4 @@ public interface DataSource<T> { * means that the row is not currently in this data source's cache. */ public RowHandle<T> getHandle(T row); - - /** - * Retrieves the index for given row object. - * <p> - * <em>Note:</em> This method does not verify that the given row object - * exists at all in this DataSource. - * - * @param row - * the row object - * @return index of the row; or <code>-1</code> if row is not available - */ - int indexOf(T row); } diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java index 6539eb49a9..8729de4a43 100644 --- a/client/src/com/vaadin/client/ui/VTree.java +++ b/client/src/com/vaadin/client/ui/VTree.java @@ -1737,8 +1737,8 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } } + showTooltipForKeyboardNavigation(node); } - showTooltipForKeyboardNavigation(node); return true; } @@ -1763,8 +1763,8 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } } + showTooltipForKeyboardNavigation(node); } - showTooltipForKeyboardNavigation(node); return true; } diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 6977cf9e7f..501dedbaa8 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -1338,9 +1338,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, @Override public void onKeyUp(KeyUpEvent event) { - if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { - onCloseClick(); - } + // do nothing } @Override diff --git a/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java b/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java index 56e1db5c36..47e072490e 100644 --- a/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java +++ b/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java @@ -443,7 +443,16 @@ public class ListDataSource<T> implements DataSource<T> { } } - @Override + /** + * Retrieves the index for given row object. + * <p> + * <em>Note:</em> This method does not verify that the given row object + * exists at all in this DataSource. + * + * @param row + * the row object + * @return index of the row; or <code>-1</code> if row is not available + */ public int indexOf(T row) { return ds.indexOf(row); } diff --git a/server/src/com/vaadin/event/ShortcutAction.java b/server/src/com/vaadin/event/ShortcutAction.java index 09accae1c7..32b909e9f2 100644 --- a/server/src/com/vaadin/event/ShortcutAction.java +++ b/server/src/com/vaadin/event/ShortcutAction.java @@ -17,6 +17,8 @@ package com.vaadin.event; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -237,6 +239,42 @@ public class ShortcutAction extends Action { } /** + * Checks whether the shortcut can be triggered with the given combination + * of keys. + * + * @param keyCode + * potential match for the {@link KeyCode} that this shortcut + * reacts to + * @param modifierKeys + * (optional) potential matches for the {@link ModifierKey}s + * required for this shortcut to react + * @return <code>true</code> if keyCode and modifierKeys are a match, + * <code>false</code> otherwise + */ + public boolean isTriggeredBy(int keyCode, int... modifierKeys) { + boolean result = false; + if (keyCode == this.keyCode) { + if (modifierKeys == null) { + result = (modifiers == null); + } else if (modifiers != null) { + List<Integer> modifierList = new ArrayList<Integer>(); + for (int modifier : modifiers) { + modifierList.add(modifier); + } + for (int modifierKey : modifierKeys) { + if (modifierList.contains(modifierKey)) { + modifierList.remove(modifierKey); + } else { + return false; + } + } + result = modifierList.isEmpty(); + } + } + return result; + } + + /** * Key codes that can be used for shortcuts * */ diff --git a/server/src/com/vaadin/event/dd/TargetDetailsImpl.java b/server/src/com/vaadin/event/dd/TargetDetailsImpl.java index 1138215f3f..8a6ec506ba 100644 --- a/server/src/com/vaadin/event/dd/TargetDetailsImpl.java +++ b/server/src/com/vaadin/event/dd/TargetDetailsImpl.java @@ -18,6 +18,8 @@ package com.vaadin.event.dd; import java.util.HashMap; import java.util.Map; +import com.vaadin.shared.MouseEventDetails; + /** * A HashMap backed implementation of {@link TargetDetails} for terminal * implementation and for extension. @@ -41,6 +43,14 @@ public class TargetDetailsImpl implements TargetDetails { this.dropTarget = dropTarget; } + /** + * @return details about the actual event that caused the event details. + * Practically mouse move or mouse up. + */ + public MouseEventDetails getMouseEvent() { + return MouseEventDetails.deSerialize((String) getData("mouseEvent")); + } + @Override public Object getData(String key) { return data.get(key); diff --git a/server/src/com/vaadin/ui/AbsoluteLayout.java b/server/src/com/vaadin/ui/AbsoluteLayout.java index 6353a4b25d..63bbe70157 100644 --- a/server/src/com/vaadin/ui/AbsoluteLayout.java +++ b/server/src/com/vaadin/ui/AbsoluteLayout.java @@ -759,8 +759,8 @@ public class AbsoluteLayout extends AbstractLayout implements private void writePositionAttribute(Node node, String key, String symbol, Float value) { if (value != null) { - String valueString = DesignAttributeHandler.formatFloat(value - .floatValue()); + String valueString = DesignAttributeHandler.getFormatter().format( + value); node.attr(key, valueString + symbol); } } diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index 9ff6dff21e..ebe438b908 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -959,8 +959,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } // handle immediate if (attr.hasKey("immediate")) { - setImmediate(DesignAttributeHandler.parseBoolean(attr - .get("immediate"))); + setImmediate(DesignAttributeHandler.getFormatter().parse( + attr.get("immediate"), Boolean.class)); } // handle locale @@ -984,8 +984,8 @@ public abstract class AbstractComponent extends AbstractClientConnector // handle responsive if (attr.hasKey("responsive")) { - setResponsive(DesignAttributeHandler.parseBoolean(attr - .get("responsive"))); + setResponsive(DesignAttributeHandler.getFormatter().parse( + attr.get("responsive"), Boolean.class)); } // check for unsupported attributes Set<String> supported = new HashSet<String>(); @@ -1138,9 +1138,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } else if (widthAuto) { attributes.put("width-auto", "true"); } else { - String widthString = DesignAttributeHandler - .formatFloat(getWidth()) - + getWidthUnits().getSymbol(); + String widthString = DesignAttributeHandler.getFormatter() + .format(getWidth()) + getWidthUnits().getSymbol(); attributes.put("width", widthString); } @@ -1152,9 +1151,8 @@ public abstract class AbstractComponent extends AbstractClientConnector } else if (heightAuto) { attributes.put("height-auto", "true"); } else { - String heightString = DesignAttributeHandler - .formatFloat(getHeight()) - + getHeightUnits().getSymbol(); + String heightString = DesignAttributeHandler.getFormatter() + .format(getHeight()) + getHeightUnits().getSymbol(); attributes.put("height", heightString); } } diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java index 67bcfc904c..3aec3b2d7a 100644 --- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java +++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java @@ -563,8 +563,8 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements if (expandRatio == 1.0f) { childElement.attr(":expand", ""); } else if (expandRatio > 0) { - childElement.attr(":expand", - DesignAttributeHandler.formatFloat(expandRatio)); + childElement.attr(":expand", DesignAttributeHandler + .getFormatter().format(expandRatio)); } } } diff --git a/server/src/com/vaadin/ui/Calendar.java b/server/src/com/vaadin/ui/Calendar.java index 206cc01d1a..e90d80072f 100644 --- a/server/src/com/vaadin/ui/Calendar.java +++ b/server/src/com/vaadin/ui/Calendar.java @@ -1457,7 +1457,7 @@ public class Calendar extends AbstractComponent implements @Override public TargetDetails translateDropTargetDetails( Map<String, Object> clientVariables) { - Map<String, Object> serverVariables = new HashMap<String, Object>(1); + Map<String, Object> serverVariables = new HashMap<String, Object>(); if (clientVariables.containsKey("dropSlotIndex")) { int slotIndex = (Integer) clientVariables.get("dropSlotIndex"); @@ -1477,6 +1477,7 @@ public class Calendar extends AbstractComponent implements currentCalendar.add(java.util.Calendar.DATE, dayIndex); serverVariables.put("dropDay", currentCalendar.getTime()); } + serverVariables.put("mouseEvent", clientVariables.get("mouseEvent")); CalendarTargetDetails td = new CalendarTargetDetails(serverVariables, this); diff --git a/server/src/com/vaadin/ui/DateField.java b/server/src/com/vaadin/ui/DateField.java index 3d683f4902..422b1ffdd8 100644 --- a/server/src/com/vaadin/ui/DateField.java +++ b/server/src/com/vaadin/ui/DateField.java @@ -24,6 +24,9 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.logging.Logger; + +import org.jsoup.nodes.Element; import com.vaadin.data.Property; import com.vaadin.data.Validator; @@ -40,6 +43,8 @@ import com.vaadin.server.PaintTarget; import com.vaadin.shared.ui.datefield.DateFieldConstants; import com.vaadin.shared.ui.datefield.Resolution; import com.vaadin.shared.ui.datefield.TextualDateFieldState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; /** * <p> @@ -1061,4 +1066,40 @@ public class DateField extends AbstractField<Date> implements } } + + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + if (design.hasAttr("value") && !design.attr("value").isEmpty()) { + Date date = DesignAttributeHandler.getFormatter().parse( + design.attr("value"), Date.class); + // formatting will return null if it cannot parse the string + if (date == null) { + Logger.getLogger(DateField.class.getName()).info( + "cannot parse " + design.attr("value") + " as date"); + } + this.setValue(date); + } + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + if (getValue() != null) { + design.attr("value", + DesignAttributeHandler.getFormatter().format(getValue())); + } + } + + /** + * Returns current date-out-of-range error message. + * + * @see #setDateOutOfRangeMessage(String) + * @since 7.4 + * @return Current error message for dates out of range. + */ + public String getDateOutOfRangeMessage() { + return dateOutOfRangeMessage; + } + } diff --git a/server/src/com/vaadin/ui/DragAndDropWrapper.java b/server/src/com/vaadin/ui/DragAndDropWrapper.java index 0e2e8f6d2f..6e4ec903d2 100644 --- a/server/src/com/vaadin/ui/DragAndDropWrapper.java +++ b/server/src/com/vaadin/ui/DragAndDropWrapper.java @@ -132,15 +132,6 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, } /** - * @return details about the actual event that caused the event details. - * Practically mouse move or mouse up. - */ - public MouseEventDetails getMouseEvent() { - return MouseEventDetails - .deSerialize((String) getData("mouseEvent")); - } - - /** * @return a detail about the drags vertical position over the wrapper. */ public VerticalDropLocation getVerticalDropLocation() { diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 35583c6052..653b620746 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -18,6 +18,9 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Map; import com.vaadin.event.FieldEvents.BlurEvent; @@ -120,6 +123,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, super(caption, content); registerRpc(rpc); setSizeUndefined(); + setCloseShortcut(ShortcutAction.KeyCode.ESCAPE); } /* ********************************************************************* */ @@ -806,14 +810,48 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, /* * Actions */ - protected CloseShortcut closeShortcut; + private LinkedHashSet<CloseShortcut> closeShortcuts = new LinkedHashSet<CloseShortcut>(); + + protected Collection<CloseShortcut> getCloseShortcuts() { + return Collections.unmodifiableCollection(closeShortcuts); + } + + /** + * Adds a keyboard shortcut for closing the window when user presses the + * given {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> + * Note that this shortcut only reacts while the window has focus, closing + * itself - if you want to close a window from a UI, use + * {@link UI#addAction(com.vaadin.event.Action)} of the UI instead. + * <p> + * If there is a prior CloseShortcut with the same keycode and modifiers, + * that gets removed before the new one is added. Prior CloseShortcuts with + * differing keycodes or modifiers are not affected. + * + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut, null for + * none + */ + public void addCloseShortcut(int keyCode, int... modifiers) { + // make sure there are no duplicates + removeCloseShortcut(keyCode, modifiers); + CloseShortcut closeShortcut = new CloseShortcut(this, keyCode, + modifiers); + closeShortcuts.add(closeShortcut); + addAction(closeShortcut); + } /** - * Makes is possible to close the window by pressing the given - * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> + * Sets the keyboard shortcut for closing the window when user presses the + * given {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> * Note that this shortcut only reacts while the window has focus, closing * itself - if you want to close a window from a UI, use * {@link UI#addAction(com.vaadin.event.Action)} of the UI instead. + * <p> + * If there are any prior CloseShortcuts when this method is called those + * get removed before the new one is added. <b>NOTE: this also removes the + * default shortcut that is added for accessibility purposes.</b> * * @param keyCode * the keycode for invoking the shortcut @@ -822,22 +860,61 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * none */ public void setCloseShortcut(int keyCode, int... modifiers) { - if (closeShortcut != null) { - removeAction(closeShortcut); + removeCloseShortcuts(); + addCloseShortcut(keyCode, modifiers); + } + + /** + * Removes a keyboard shortcut previously set with + * {@link #setCloseShortcut(int, int...)} or + * {@link #addCloseShortcut(int, int...)}. + * + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut, null for + * none + */ + public void removeCloseShortcut(int keyCode, int... modifiers) { + for (CloseShortcut closeShortcut : closeShortcuts) { + if (closeShortcut.isTriggeredBy(keyCode, modifiers)) { + removeAction(closeShortcut); + closeShortcuts.remove(closeShortcut); + break; + } } - closeShortcut = new CloseShortcut(this, keyCode, modifiers); - addAction(closeShortcut); } /** - * Removes the keyboard shortcut previously set with - * {@link #setCloseShortcut(int, int...)}. + * @deprecated use {@link #resetCloseShortcuts()} instead, or + * {@link #removeCloseShortcuts()} if you also want to get rid + * of the default shortcut */ + @Deprecated public void removeCloseShortcut() { - if (closeShortcut != null) { + resetCloseShortcuts(); + } + + /** + * Removes all the keyboard shortcuts previously set with + * {@link #setCloseShortcut(int, int...)} or + * {@link #addCloseShortcut(int, int...)} and re-adds the default shortcut + * {@link KeyCode.ESCAPE}. + */ + public void resetCloseShortcuts() { + setCloseShortcut(ShortcutAction.KeyCode.ESCAPE); + } + + /** + * Removes all the keyboard shortcuts previously set with + * {@link #setCloseShortcut(int, int...)} or + * {@link #addCloseShortcut(int, int...)}. + */ + public void removeCloseShortcuts() { + for (CloseShortcut closeShortcut : closeShortcuts) { removeAction(closeShortcut); - closeShortcut = null; } + closeShortcuts.clear(); } /** diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java index be7d023ebf..3e2c01c881 100644 --- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java +++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java @@ -19,35 +19,25 @@ import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; -import java.io.File; import java.io.Serializable; import java.lang.reflect.Method; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; -import com.vaadin.event.ShortcutAction; -import com.vaadin.event.ShortcutAction.KeyCode; -import com.vaadin.event.ShortcutAction.ModifierKey; -import com.vaadin.server.ExternalResource; -import com.vaadin.server.FileResource; -import com.vaadin.server.FontAwesome; -import com.vaadin.server.Resource; -import com.vaadin.server.ThemeResource; +import com.vaadin.data.util.converter.Converter; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; @@ -65,8 +55,21 @@ public class DesignAttributeHandler implements Serializable { return Logger.getLogger(DesignAttributeHandler.class.getName()); } - private static Map<Class, AttributeCacheEntry> cache = Collections - .synchronizedMap(new HashMap<Class, AttributeCacheEntry>()); + private static Map<Class<?>, AttributeCacheEntry> cache = Collections + .synchronizedMap(new HashMap<Class<?>, AttributeCacheEntry>()); + + // translates string <-> object + private static DesignFormatter FORMATTER = new DesignFormatter(); + + /** + * Returns the currently used formatter. All primitive types and all types + * needed by Vaadin components are handled by that formatter. + * + * @return An instance of the formatter. + */ + public static DesignFormatter getFormatter() { + return FORMATTER; + } /** * Clears the children and attributes of the given element @@ -111,8 +114,8 @@ public class DesignAttributeHandler implements Serializable { success = false; } else { // we have a value from design attributes, let's use that - Object param = fromAttributeValue( - setter.getParameterTypes()[0], value); + Object param = getFormatter().parse(value, + setter.getParameterTypes()[0]); setter.invoke(target, param); success = true; } @@ -170,7 +173,7 @@ public class DesignAttributeHandler implements Serializable { Method getter = descriptor.getReadMethod(); Method setter = descriptor.getWriteMethod(); if (getter != null && setter != null - && isSupported(descriptor.getPropertyType())) { + && getFormatter().canConvert(descriptor.getPropertyType())) { String attribute = toAttributeName(descriptor.getName()); entry.addAttribute(attribute, getter, setter); } @@ -229,10 +232,9 @@ public class DesignAttributeHandler implements Serializable { * @return the attribute value or the default value if the attribute is not * found */ - @SuppressWarnings("unchecked") public static <T> T readAttribute(String attribute, Attributes attributes, Class<T> outputType) { - if (!isSupported(outputType)) { + if (!getFormatter().canConvert(outputType)) { throw new IllegalArgumentException("output type: " + outputType.getName() + " not supported"); } @@ -241,7 +243,7 @@ public class DesignAttributeHandler implements Serializable { } else { try { String value = attributes.get(attribute); - return (T) fromAttributeValue(outputType, value); + return getFormatter().parse(value, outputType); } catch (Exception e) { throw new DesignException("Failed to read attribute " + attribute, e); @@ -266,7 +268,7 @@ public class DesignAttributeHandler implements Serializable { */ public static <T> void writeAttribute(String attribute, Attributes attributes, T value, T defaultValue, Class<T> inputType) { - if (!isSupported(inputType)) { + if (!getFormatter().canConvert(inputType)) { throw new IllegalArgumentException("input type: " + inputType.getName() + " not supported"); } @@ -277,101 +279,6 @@ public class DesignAttributeHandler implements Serializable { } /** - * Formats the given design attribute value. The method is provided to - * ensure consistent number formatting for design attribute values - * - * @param number - * the number to be formatted - * @return the formatted number - */ - public static String formatFloat(float number) { - return getDecimalFormat().format(number); - } - - /** - * Formats the given design attribute value. The method is provided to - * ensure consistent number formatting for design attribute values - * - * @param number - * the number to be formatted - * @return the formatted number - */ - public static String formatDouble(double number) { - return getDecimalFormat().format(number); - } - - /** - * Convert ShortcutAction to attribute string presentation - * - * @param shortcut - * the shortcut action - * @return the action as attribute string presentation - */ - private static String formatShortcutAction(ShortcutAction shortcut) { - StringBuilder sb = new StringBuilder(); - // handle modifiers - if (shortcut.getModifiers() != null) { - for (int modifier : shortcut.getModifiers()) { - sb.append(ShortcutKeyMapper.getStringForKeycode(modifier)) - .append("-"); - } - } - // handle keycode - sb.append(ShortcutKeyMapper.getStringForKeycode(shortcut.getKeyCode())); - return sb.toString(); - } - - /** - * Reads shortcut action from attribute presentation - * - * @param attributeValue - * attribute presentation of shortcut action - * @return shortcut action with keycode and modifier keys from attribute - * value - */ - private static ShortcutAction readShortcutAction(String attributeValue) { - if (attributeValue.length() == 0) { - return null; - } - String[] parts = attributeValue.split("-"); - // handle keycode - String keyCodePart = parts[parts.length - 1]; - int keyCode = ShortcutKeyMapper.getKeycodeForString(keyCodePart); - if (keyCode < 0) { - throw new IllegalArgumentException("Invalid shortcut definition " - + attributeValue); - } - // handle modifiers - int[] modifiers = null; - if (parts.length > 1) { - modifiers = new int[parts.length - 1]; - } - for (int i = 0; i < parts.length - 1; i++) { - int modifier = ShortcutKeyMapper.getKeycodeForString(parts[i]); - if (modifier > 0) { - modifiers[i] = modifier; - } else { - throw new IllegalArgumentException( - "Invalid shortcut definition " + attributeValue); - } - } - return new ShortcutAction(null, keyCode, modifiers); - } - - /** - * Creates the decimal format used when writing attributes to the design. - * - * @return the decimal format - */ - private static DecimalFormat getDecimalFormat() { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale( - "en_US")); - DecimalFormat fmt = new DecimalFormat("0.###", symbols); - fmt.setGroupingUsed(false); - return fmt; - } - - /** * Returns the design attribute name corresponding the given method name. * For example given a method name <code>setPrimaryStyleName</code> the * return value would be <code>primary-style-name</code> @@ -381,6 +288,7 @@ public class DesignAttributeHandler implements Serializable { * @return the design attribute name corresponding the given method name */ private static String toAttributeName(String propertyName) { + propertyName = removeSubsequentUppercase(propertyName); String[] words = propertyName.split("(?<!^)(?=[A-Z])"); StringBuilder builder = new StringBuilder(); for (int i = 0; i < words.length; i++) { @@ -393,56 +301,43 @@ public class DesignAttributeHandler implements Serializable { } /** - * Parses the given attribute value to specified target type + * Replaces subsequent UPPERCASE strings of length 2 or more followed either + * by another uppercase letter or an end of string. This is to generalise + * handling of method names like <tt>showISOWeekNumbers</tt>. * - * @param targetType - * the target type for the value - * @param value - * the parsed value - * @return the object of specified target type + * @param param + * Input string. + * @return Input string with sequences of UPPERCASE turned into Normalcase. */ - private static Object fromAttributeValue(Class<?> targetType, String value) { - if (targetType == String.class) { - return value; - } - // special handling for boolean type. The attribute evaluates to true if - // it is present and the value is not "false" or "FALSE". Thus empty - // value evaluates to true. - if (targetType == Boolean.TYPE || targetType == Boolean.class) { - return parseBoolean(value); - } - if (targetType == Integer.TYPE || targetType == Integer.class) { - return Integer.valueOf(value); - } - if (targetType == Byte.TYPE || targetType == Byte.class) { - return Byte.valueOf(value); - } - if (targetType == Short.TYPE || targetType == Short.class) { - return Short.valueOf(value); - } - if (targetType == Long.TYPE || targetType == Long.class) { - return Long.valueOf(value); - } - if (targetType == Character.TYPE || targetType == Character.class) { - return value.charAt(0); - } - if (targetType == Float.TYPE || targetType == Float.class) { - return Float.valueOf(value); - } - if (targetType == Double.TYPE || targetType == Double.class) { - return Double.valueOf(value); - } - if (targetType == Resource.class) { - return parseResource(value); - } - if (Enum.class.isAssignableFrom(targetType)) { - return Enum.valueOf((Class<? extends Enum>) targetType, - value.toUpperCase()); - } - if (targetType == ShortcutAction.class) { - return readShortcutAction(value); + private static String removeSubsequentUppercase(String param) { + StringBuffer result = new StringBuffer(); + // match all two-or-more caps letters lead by a non-uppercase letter + // followed by either a capital letter or string end + Pattern pattern = Pattern.compile("(^|[^A-Z])([A-Z]{2,})([A-Z]|$)"); + Matcher matcher = pattern.matcher(param); + while (matcher.find()) { + String matched = matcher.group(2); + // if this is a beginning of the string, the whole matched group is + // written in lower case + if (matcher.group(1).isEmpty()) { + matcher.appendReplacement(result, matched.toLowerCase() + + matcher.group(3)); + // otherwise the first character of the group stays uppercase, + // while the others are lower case + } else { + matcher.appendReplacement( + result, + matcher.group(1) + matched.substring(0, 1) + + matched.substring(1).toLowerCase() + + matcher.group(3)); + } + // in both cases the uppercase letter of the next word (or string's + // end) is added + // this implies there is at least one extra lowercase letter after + // it to be caught by the next call to find() } - return null; + matcher.appendTail(result); + return result.toString(); } /** @@ -460,57 +355,16 @@ public class DesignAttributeHandler implements Serializable { // value is not null. How to represent null value in attributes? return ""; } - if (sourceType == Resource.class) { - if (value instanceof ExternalResource) { - return ((ExternalResource) value).getURL(); - } else if (value instanceof ThemeResource) { - return "theme://" + ((ThemeResource) value).getResourceId(); - } else if (value instanceof FontAwesome) { - return "font://" + ((FontAwesome) value).name(); - } else if (value instanceof FileResource) { - String path = ((FileResource) value).getSourceFile().getPath(); - if (File.separatorChar != '/') { - // make sure we use '/' as file separator in templates - return path.replace(File.separatorChar, '/'); - } else { - return path; - } - } else { - getLogger().warning( - "Unknown resource type " + value.getClass().getName()); - return null; - } - } else if (sourceType == Float.class || sourceType == Float.TYPE) { - return formatFloat(((Float) value).floatValue()); - } else if (sourceType == Double.class || sourceType == Double.TYPE) { - return formatDouble(((Double) value).doubleValue()); - } else if (sourceType == ShortcutAction.class) { - return formatShortcutAction((ShortcutAction) value); + Converter<String, Object> converter = getFormatter().findConverterFor( + sourceType); + if (converter != null) { + return converter.convertToPresentation(value, String.class, null); } else { return value.toString(); } } /** - * Parses the given attribute value as resource - * - * @param value - * the attribute value to be parsed - * @return resource instance based on the attribute value - */ - private static Resource parseResource(String value) { - if (value.startsWith("http://")) { - return new ExternalResource(value); - } else if (value.startsWith("theme://")) { - return new ThemeResource(value.substring(8)); - } else if (value.startsWith("font://")) { - return FontAwesome.valueOf(value.substring(7)); - } else { - return new FileResource(new File(value)); - } - } - - /** * Returns a setter that can be used for assigning the given design * attribute to the class * @@ -542,29 +396,6 @@ public class DesignAttributeHandler implements Serializable { return cache.get(clazz).getGetter(attribute); } - // supported property types - private static final List<Class<?>> supportedClasses = Arrays - .asList(new Class<?>[] { String.class, Boolean.class, - Integer.class, Byte.class, Short.class, Long.class, - Character.class, Float.class, Double.class, Resource.class, - ShortcutAction.class }); - - /** - * Returns true if the specified value type is supported by this class. - * Currently the handler supports primitives, {@link Locale.class} and - * {@link Resource.class}. - * - * @param valueType - * the value type to be tested - * @return true if the value type is supported, otherwise false - */ - private static boolean isSupported(Class<?> valueType) { - return valueType != null - && (valueType.isPrimitive() - || supportedClasses.contains(valueType) || Enum.class - .isAssignableFrom(valueType)); - } - /** * Cache object for caching supported attributes and their getters and * setters @@ -599,117 +430,4 @@ public class DesignAttributeHandler implements Serializable { } } - /** - * Provides mappings between shortcut keycodes and their representation in - * design attributes - * - * @author Vaadin Ltd - */ - private static class ShortcutKeyMapper implements Serializable { - - private static Map<Integer, String> keyCodeMap = Collections - .synchronizedMap(new HashMap<Integer, String>()); - private static Map<String, Integer> presentationMap = Collections - .synchronizedMap(new HashMap<String, Integer>()); - - static { - // map modifiers - mapKey(ModifierKey.ALT, "alt"); - mapKey(ModifierKey.CTRL, "ctrl"); - mapKey(ModifierKey.META, "meta"); - mapKey(ModifierKey.SHIFT, "shift"); - // map keys - mapKey(KeyCode.ENTER, "enter"); - mapKey(KeyCode.ESCAPE, "escape"); - mapKey(KeyCode.PAGE_UP, "pageup"); - mapKey(KeyCode.PAGE_DOWN, "pagedown"); - mapKey(KeyCode.TAB, "tab"); - mapKey(KeyCode.ARROW_LEFT, "left"); - mapKey(KeyCode.ARROW_UP, "up"); - mapKey(KeyCode.ARROW_RIGHT, "right"); - mapKey(KeyCode.ARROW_DOWN, "down"); - mapKey(KeyCode.BACKSPACE, "backspace"); - mapKey(KeyCode.DELETE, "delete"); - mapKey(KeyCode.INSERT, "insert"); - mapKey(KeyCode.END, "end"); - mapKey(KeyCode.HOME, "home"); - mapKey(KeyCode.F1, "f1"); - mapKey(KeyCode.F2, "f2"); - mapKey(KeyCode.F3, "f3"); - mapKey(KeyCode.F4, "f4"); - mapKey(KeyCode.F5, "f5"); - mapKey(KeyCode.F6, "f6"); - mapKey(KeyCode.F7, "f7"); - mapKey(KeyCode.F8, "f8"); - mapKey(KeyCode.F9, "f9"); - mapKey(KeyCode.F10, "f10"); - mapKey(KeyCode.F11, "f11"); - mapKey(KeyCode.F12, "f12"); - mapKey(KeyCode.NUM0, "0"); - mapKey(KeyCode.NUM1, "1"); - mapKey(KeyCode.NUM2, "2"); - mapKey(KeyCode.NUM3, "3"); - mapKey(KeyCode.NUM4, "4"); - mapKey(KeyCode.NUM5, "5"); - mapKey(KeyCode.NUM6, "6"); - mapKey(KeyCode.NUM7, "7"); - mapKey(KeyCode.NUM8, "8"); - mapKey(KeyCode.NUM9, "9"); - mapKey(KeyCode.SPACEBAR, "spacebar"); - mapKey(KeyCode.A, "a"); - mapKey(KeyCode.B, "b"); - mapKey(KeyCode.C, "c"); - mapKey(KeyCode.D, "d"); - mapKey(KeyCode.E, "e"); - mapKey(KeyCode.F, "f"); - mapKey(KeyCode.G, "g"); - mapKey(KeyCode.H, "h"); - mapKey(KeyCode.I, "i"); - mapKey(KeyCode.J, "j"); - mapKey(KeyCode.K, "k"); - mapKey(KeyCode.L, "l"); - mapKey(KeyCode.M, "m"); - mapKey(KeyCode.N, "n"); - mapKey(KeyCode.O, "o"); - mapKey(KeyCode.P, "p"); - mapKey(KeyCode.Q, "q"); - mapKey(KeyCode.R, "r"); - mapKey(KeyCode.S, "s"); - mapKey(KeyCode.T, "t"); - mapKey(KeyCode.U, "u"); - mapKey(KeyCode.V, "v"); - mapKey(KeyCode.X, "x"); - mapKey(KeyCode.Y, "y"); - mapKey(KeyCode.Z, "z"); - } - - private static void mapKey(int keyCode, String presentation) { - keyCodeMap.put(keyCode, presentation); - presentationMap.put(presentation, keyCode); - } - - private static int getKeycodeForString(String attributePresentation) { - Integer code = presentationMap.get(attributePresentation); - return code != null ? code.intValue() : -1; - } - - private static String getStringForKeycode(int keyCode) { - return keyCodeMap.get(keyCode); - } - } - - /** - * Converts the given string attribute value to its corresponding boolean. - * - * An empty string and "true" are considered to represent a true value and - * "false" to represent a false value. - * - * @param booleanValue - * the boolean value from an attribute - * @return the parsed boolean - */ - public static boolean parseBoolean(String booleanValue) { - return !booleanValue.equalsIgnoreCase("false"); - } - }
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index fd83339b76..b298c95320 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import org.jsoup.nodes.Attributes; @@ -29,6 +28,7 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import com.vaadin.annotations.DesignRoot; +import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; import com.vaadin.ui.HasComponents; @@ -509,7 +509,7 @@ public class DesignContext implements Serializable { // Otherwise, get the full class name using the prefix to package // mapping. Example: "v-vertical-layout" -> // "com.vaadin.ui.VerticalLayout" - String[] parts = tagName.split("-"); + String[] parts = tagName.split("-", 2); if (parts.length < 2) { throw new DesignException("The tagname '" + tagName + "' is invalid: missing prefix."); @@ -519,24 +519,16 @@ public class DesignContext implements Serializable { if (packageName == null) { throw new DesignException("Unknown tag: " + tagName); } - int firstCharacterIndex = prefixName.length() + 1; // +1 is for '-' - tagName = tagName.substring(firstCharacterIndex, - firstCharacterIndex + 1).toUpperCase(Locale.ENGLISH) - + tagName.substring(firstCharacterIndex + 1); - int i; - while ((i = tagName.indexOf("-")) != -1) { - int length = tagName.length(); - if (i != length - 1) { - tagName = tagName.substring(0, i) - + tagName.substring(i + 1, i + 2).toUpperCase( - Locale.ENGLISH) + tagName.substring(i + 2); - - } else { - // Ends with "-" - System.out.println("A tag name should not end with '-'."); - } + String[] classNameParts = parts[1].split("-"); + String className = ""; + for (String classNamePart : classNameParts) { + // Split will ignore trailing and multiple dashes but that should be + // ok + // <v-button--> will be resolved to <v-button> + // <v--button> will be resolved to <v-button> + className += SharedUtil.capitalize(classNamePart); } - return packageName + "." + tagName; + return packageName + "." + className; } @SuppressWarnings("unchecked") diff --git a/server/src/com/vaadin/ui/declarative/DesignFormatter.java b/server/src/com/vaadin/ui/declarative/DesignFormatter.java new file mode 100644 index 0000000000..fdce563104 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/DesignFormatter.java @@ -0,0 +1,348 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.event.ShortcutAction; +import com.vaadin.server.Resource; +import com.vaadin.ui.declarative.converters.DesignDateConverter; +import com.vaadin.ui.declarative.converters.DesignFormatConverter; +import com.vaadin.ui.declarative.converters.DesignResourceConverter; +import com.vaadin.ui.declarative.converters.DesignShortcutActionConverter; +import com.vaadin.ui.declarative.converters.ShortcutKeyMapper; +import com.vaadin.ui.declarative.converters.DesignToStringConverter; + +/** + * Class focused on flexible and consistent formatting and parsing of different + * values throughout reading and writing {@link Design}. An instance of this + * class is used by {@link DesignAttributeHandler}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignFormatter implements Serializable { + + private final Map<Class<?>, Converter<String, ?>> converterMap = Collections + .synchronizedMap(new HashMap<Class<?>, Converter<String, ?>>()); + + /** + * Creates the formatter with default types already mapped. + */ + public DesignFormatter() { + mapDefaultTypes(); + } + + /** + * Maps default types to their converters. + * + */ + protected void mapDefaultTypes() { + // numbers use standard toString/valueOf approach + for (Class<?> c : new Class<?>[] { Integer.class, Byte.class, + Short.class, Long.class, BigDecimal.class }) { + DesignToStringConverter<?> conv = new DesignToStringConverter(c); + converterMap.put(c, conv); + try { + converterMap.put((Class<?>) c.getField("TYPE").get(null), conv); + } catch (Exception e) { + ; // this will never happen + } + } + // booleans use a bit different converter than the standard one + // "false" is boolean false, everything else is boolean true + Converter<String, Boolean> booleanConverter = new Converter<String, Boolean>() { + + @Override + public Boolean convertToModel(String value, + Class<? extends Boolean> targetType, Locale locale) + throws Converter.ConversionException { + return !value.equalsIgnoreCase("false"); + } + + @Override + public String convertToPresentation(Boolean value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return String.valueOf(value.booleanValue()); + } + + @Override + public Class<Boolean> getModelType() { + return Boolean.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + }; + converterMap.put(Boolean.class, booleanConverter); + converterMap.put(boolean.class, booleanConverter); + + // floats and doubles use formatters + DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale( + "en_US")); + DecimalFormat fmt = new DecimalFormat("0.###", symbols); + fmt.setGroupingUsed(false); + converterMap.put(Float.class, new DesignFormatConverter<Float>( + Float.class, fmt)); + converterMap.put(Float.TYPE, new DesignFormatConverter<Float>( + Float.class, fmt)); + converterMap.put(Double.class, new DesignFormatConverter<Double>( + Double.class, fmt)); + converterMap.put(Double.TYPE, new DesignFormatConverter<Double>( + Double.class, fmt)); + + // strings do nothing + converterMap.put(String.class, new Converter<String, String>() { + + @Override + public String convertToModel(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value; + } + + @Override + public String convertToPresentation(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value; + } + + @Override + public Class<String> getModelType() { + return String.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + }); + + // char takes the first character from the string + Converter<String, Character> charConverter = new DesignToStringConverter<Character>( + Character.class) { + + @Override + public Character convertToModel(String value, + Class<? extends Character> targetType, Locale locale) + throws Converter.ConversionException { + return value.charAt(0); + } + + }; + converterMap.put(Character.class, charConverter); + converterMap.put(Character.TYPE, charConverter); + + // date conversion has its own class + converterMap.put(Date.class, new DesignDateConverter()); + + // as shortcut action and resource + converterMap.put(ShortcutAction.class, + new DesignShortcutActionConverter(ShortcutKeyMapper.DEFAULT)); + + converterMap.put(Resource.class, new DesignResourceConverter()); + + // timezones use different static method and do not use toString() + converterMap.put(TimeZone.class, new DesignToStringConverter<TimeZone>( + TimeZone.class, "getTimeZone") { + @Override + public String convertToPresentation(TimeZone value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return value.getID(); + } + }); + } + + /** + * Adds a converter for a new type. + * + * @param converter + * Converter to add. + */ + protected <T> void addConverter(Converter<String, T> converter) { + converterMap.put(converter.getModelType(), converter); + } + + /** + * Adds a converter for a given type. + * + * @param type + * Type to convert to/from. + * @param converter + * Converter. + */ + protected <T> void addConverter(Class<?> type, + Converter<String, ?> converter) { + converterMap.put(type, converter); + } + + /** + * Removes the converter for given type, if it was present. + * + * @param type + * Type to remove converter for. + */ + protected void removeConverter(Class<?> type) { + converterMap.remove(type); + } + + /** + * Returns a set of classes that have a converter registered. This is <b>not + * the same</b> as the list of supported classes - subclasses of classes in + * this set are also supported. + * + * @return An unmodifiable set of classes that have a converter registered. + */ + protected Set<Class<?>> getRegisteredClasses() { + return Collections.unmodifiableSet(converterMap.keySet()); + } + + /** + * Parses a given string as a value of given type + * + * @param value + * String value to convert. + * @param type + * Expected result type. + * @return String converted to the expected result type using a registered + * converter for that type. + */ + public <T> T parse(String value, Class<? extends T> type) { + Converter<String, T> converter = findConverterFor(type); + if (converter != null) { + return converter.convertToModel(value, type, null); + } else { + return null; + } + } + + /** + * Finds a formatter for a given object and attempts to format it. + * + * @param object + * Object to format. + * @return String representation of the object, as returned by the + * registered converter. + */ + public String format(Object object) { + return format(object, object == null ? Object.class : object.getClass()); + } + + /** + * Formats an object according to a converter suitable for a given type. + * + * @param object + * Object to format. + * @param type + * Type of the object. + * @return String representation of the object, as returned by the + * registered converter. + */ + public <T> String format(T object, Class<? extends T> type) { + if (object == null) { + return null; + } else { + return findConverterFor(object.getClass()).convertToPresentation( + object, String.class, null); + } + } + + /** + * Checks whether or not a value of a given type can be converted. If a + * converter for a superclass is found, this will return true. + * + * @param type + * Type to check. + * @return <b>true</b> when either a given type or its supertype has a + * converter, <b>false</b> otherwise. + */ + public boolean canConvert(Class<?> type) { + return findConverterFor(type) != null; + } + + /** + * Finds a converter for a given type. May return a converter for a + * superclass instead, if one is found and {@code strict} is false. + * + * @param sourceType + * Type to find a converter for. + * @param strict + * Whether or not search should be strict. When this is + * <b>false</b>, a converter for a superclass of given type may + * be returned. + * @return A valid converter for a given type or its supertype, <b>null</b> + * if it was not found. + */ + @SuppressWarnings("unchecked") + protected <T> Converter<String, T> findConverterFor( + Class<? extends T> sourceType, boolean strict) { + if (sourceType.isEnum()) { + // enums can be read in lowercase + return new DesignToStringConverter<T>(sourceType) { + + @Override + public T convertToModel(String value, + Class<? extends T> targetType, Locale locale) + throws Converter.ConversionException { + return super.convertToModel(value.toUpperCase(), + targetType, locale); + } + }; + } else if (converterMap.containsKey(sourceType)) { + return ((Converter<String, T>) converterMap.get(sourceType)); + } else if (!strict) { + for (Class<?> supported : converterMap.keySet()) { + if (supported.isAssignableFrom(sourceType)) { + return ((Converter<String, T>) converterMap.get(supported)); + } + } + } + return null; + } + + /** + * Finds a converter for a given type. May return a converter for a + * superclass instead, if one is found. + * + * @param sourceType + * Type to find a converter for. + * @return A valid converter for a given type or its subtype, <b>null</b> if + * it was not found. + */ + protected <T> Converter<String, T> findConverterFor( + Class<? extends T> sourceType) { + return findConverterFor(sourceType, false); + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java new file mode 100644 index 0000000000..d2d63ad16e --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignDateConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * A date converter to be used by {@link DesignAttributeHandler}. Provides + * ISO-compliant way of storing date and time. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignDateConverter implements Converter<String, Date> { + + @Override + public Date convertToModel(String value, Class<? extends Date> targetType, + Locale locale) throws Converter.ConversionException { + for (String pattern : new String[] { "yyyy-MM-dd HH:mm:ssZ", + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH", + "yyyy-MM-dd", "yyyy-MM", "yyyy" }) { + try { + return new SimpleDateFormat(pattern).parse(value); + } catch (ParseException e) { + // not parseable, ignore and try another format + } + } + return null; + } + + @Override + public String convertToPresentation(Date value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ").format(value); + } + + @Override + public Class<Date> getModelType() { + return Date.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java new file mode 100644 index 0000000000..e9b26fce0b --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignFormatConverter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.text.Format; +import java.text.ParseException; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; + +/** + * Converter based on Java Formats rather than static methods. + * + * @since 7.4 + * @author Vaadin Ltd + * @param <TYPE> + * Type of the object to format. + */ +public class DesignFormatConverter<TYPE> implements Converter<String, TYPE> { + + private final Format format; + private final Class<? extends TYPE> type; + + /** + * Constructs an instance of the converter. + */ + public DesignFormatConverter(Class<? extends TYPE> type, Format format) { + this.type = type; + this.format = format; + } + + @Override + public TYPE convertToModel(String value, Class<? extends TYPE> targetType, + Locale locale) throws Converter.ConversionException { + try { + return targetType.cast(this.format.parseObject(value)); + } catch (ParseException e) { + throw new Converter.ConversionException(e); + } + } + + @Override + public String convertToPresentation(TYPE value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return this.format.format(value); + } + + @Override + public Class<TYPE> getModelType() { + return (Class<TYPE>) this.type; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java new file mode 100644 index 0000000000..70e46b8e7f --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignResourceConverter.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.io.File; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; +import com.vaadin.server.ThemeResource; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * A converter for {@link Resource} implementations supported by + * {@link DesignAttributeHandler}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignResourceConverter implements Converter<String, Resource> { + + @Override + public Resource convertToModel(String value, + Class<? extends Resource> targetType, Locale locale) + throws Converter.ConversionException { + if (value.startsWith("http://")) { + return new ExternalResource(value); + } else if (value.startsWith("theme://")) { + return new ThemeResource(value.substring(8)); + } else if (value.startsWith("font://")) { + return FontAwesome.valueOf(value.substring(7)); + } else { + return new FileResource(new File(value)); + } + } + + @Override + public String convertToPresentation(Resource value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + if (value instanceof ExternalResource) { + return ((ExternalResource) value).getURL(); + } else if (value instanceof ThemeResource) { + return "theme://" + ((ThemeResource) value).getResourceId(); + } else if (value instanceof FontAwesome) { + return "font://" + ((FontAwesome) value).name(); + } else if (value instanceof FileResource) { + String path = ((FileResource) value).getSourceFile().getPath(); + if (File.separatorChar != '/') { + // make sure we use '/' as file separator in templates + return path.replace(File.separatorChar, '/'); + } else { + return path; + } + } else { + throw new Converter.ConversionException("unknown Resource type - " + + value.getClass().getName()); + } + } + + @Override + public Class<Resource> getModelType() { + return Resource.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java new file mode 100644 index 0000000000..d9d84a1263 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java @@ -0,0 +1,121 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.event.ShortcutAction; + +/** + * Converter for {@link ShortcutActions}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignShortcutActionConverter implements + Converter<String, ShortcutAction> { + + /** + * Default instance of the shortcut key mapper. + */ + private final ShortcutKeyMapper keyMapper; + + /** + * Constructs the converter with given key mapper. + * + * @param mapper + * Key mapper to use. + */ + public DesignShortcutActionConverter(ShortcutKeyMapper mapper) { + keyMapper = mapper; + } + + @Override + public ShortcutAction convertToModel(String value, + Class<? extends ShortcutAction> targetType, Locale locale) + throws Converter.ConversionException { + if (value.length() == 0) { + return null; + } + String[] data = value.split(" ", 2); + + String[] parts = data[0].split("-"); + // handle keycode + String keyCodePart = parts[parts.length - 1]; + int keyCode = getKeyMapper().getKeycodeForString(keyCodePart); + if (keyCode < 0) { + throw new IllegalArgumentException("Invalid shortcut definition " + + value); + } + // handle modifiers + int[] modifiers = null; + if (parts.length > 1) { + modifiers = new int[parts.length - 1]; + } + for (int i = 0; i < parts.length - 1; i++) { + int modifier = getKeyMapper().getKeycodeForString(parts[i]); + if (modifier > 0) { + modifiers[i] = modifier; + } else { + throw new IllegalArgumentException( + "Invalid shortcut definition " + value); + } + } + return new ShortcutAction(data.length == 2 ? data[1] : null, keyCode, + modifiers); + } + + @Override + public String convertToPresentation(ShortcutAction value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + StringBuilder sb = new StringBuilder(); + // handle modifiers + if (value.getModifiers() != null) { + for (int modifier : value.getModifiers()) { + sb.append(getKeyMapper().getStringForKeycode(modifier)).append( + "-"); + } + } + // handle keycode + sb.append(getKeyMapper().getStringForKeycode(value.getKeyCode())); + if (value.getCaption() != null) { + sb.append(" ").append(value.getCaption()); + } + return sb.toString(); + } + + @Override + public Class<ShortcutAction> getModelType() { + return ShortcutAction.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + + /** + * Returns the currently used key mapper. + * + * @return Key mapper. + */ + public ShortcutKeyMapper getKeyMapper() { + return keyMapper; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java new file mode 100644 index 0000000000..d80119bea1 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/DesignToStringConverter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.lang.reflect.InvocationTargetException; +import java.util.Locale; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.ui.declarative.DesignAttributeHandler; + +/** + * Utility class for {@link DesignAttributeHandler} that deals with converting + * various types to string. + * + * @since 7.4 + * @author Vaadin Ltd + * @param <TYPE> + * Type of the data being converted. + */ +public class DesignToStringConverter<TYPE> implements Converter<String, TYPE> { + + private final Class<? extends TYPE> type; + + private final String staticMethodName; + + /** + * A string that corresponds to how a null value is stored. + */ + public static final String NULL_VALUE_REPRESENTATION = ""; + + /** + * Constructs the converter for a given type. Implicitly requires that a + * static method {@code valueOf(String)} is present in the type to do the + * conversion. + * + * @param type + * Type of values to convert. + */ + public DesignToStringConverter(Class<? extends TYPE> type) { + this(type, "valueOf"); + } + + /** + * Constructs the converter for a given type, giving the name of the public + * static method that does the conversion from String. + * + * @param type + * Type to convert. + * @param staticMethodName + * Method to call when converting from String to this type. This + * must be public and static method that returns an object of + * passed type. + */ + public DesignToStringConverter(Class<? extends TYPE> type, String staticMethodName) { + this.type = type; + this.staticMethodName = staticMethodName; + } + + @Override + public TYPE convertToModel(String value, Class<? extends TYPE> targetType, + Locale locale) throws Converter.ConversionException { + try { + return type.cast(type + .getMethod(this.staticMethodName, String.class).invoke( + null, value)); + } catch (IllegalAccessException e) { + throw new Converter.ConversionException(e); + } catch (IllegalArgumentException e) { + throw new Converter.ConversionException(e); + } catch (InvocationTargetException e) { + throw new Converter.ConversionException(e); + } catch (NoSuchMethodException e) { + throw new Converter.ConversionException(e); + } catch (SecurityException e) { + throw new Converter.ConversionException(e); + } catch (RuntimeException e) { + throw new Converter.ConversionException(e); + } + } + + @Override + public String convertToPresentation(TYPE value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + if (value == null) { + return NULL_VALUE_REPRESENTATION; + } else { + return value.toString(); + } + } + + @Override + public Class<TYPE> getModelType() { + return (Class<TYPE>) this.type; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java b/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java new file mode 100644 index 0000000000..46c38ce0e0 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/converters/ShortcutKeyMapper.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.ui.declarative.converters; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; + +/** + * Provides mappings between shortcut keycodes and their representation in + * design attributes. Contains a default framework implementation as a field. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface ShortcutKeyMapper extends Serializable { + + /** + * Gets the key code for a given string. + * + * @param attributePresentation + * String + * @return Key code. + */ + public int getKeycodeForString(String attributePresentation); + + /** + * Returns a string for a given key code. + * + * @param keyCode + * Key code. + * @return String. + */ + public String getStringForKeycode(int keyCode); + + /** + * An instance of a default keymapper. + */ + public static final ShortcutKeyMapper DEFAULT = new ShortcutKeyMapper() { + + private final Map<Integer, String> keyCodeMap = Collections + .synchronizedMap(new HashMap<Integer, String>()); + private final Map<String, Integer> presentationMap = Collections + .synchronizedMap(new HashMap<String, Integer>()); + + { + // map modifiers + mapKey(ModifierKey.ALT, "alt"); + mapKey(ModifierKey.CTRL, "ctrl"); + mapKey(ModifierKey.META, "meta"); + mapKey(ModifierKey.SHIFT, "shift"); + // map keys + mapKey(KeyCode.ENTER, "enter"); + mapKey(KeyCode.ESCAPE, "escape"); + mapKey(KeyCode.PAGE_UP, "pageup"); + mapKey(KeyCode.PAGE_DOWN, "pagedown"); + mapKey(KeyCode.TAB, "tab"); + mapKey(KeyCode.ARROW_LEFT, "left"); + mapKey(KeyCode.ARROW_UP, "up"); + mapKey(KeyCode.ARROW_RIGHT, "right"); + mapKey(KeyCode.ARROW_DOWN, "down"); + mapKey(KeyCode.BACKSPACE, "backspace"); + mapKey(KeyCode.DELETE, "delete"); + mapKey(KeyCode.INSERT, "insert"); + mapKey(KeyCode.END, "end"); + mapKey(KeyCode.HOME, "home"); + mapKey(KeyCode.F1, "f1"); + mapKey(KeyCode.F2, "f2"); + mapKey(KeyCode.F3, "f3"); + mapKey(KeyCode.F4, "f4"); + mapKey(KeyCode.F5, "f5"); + mapKey(KeyCode.F6, "f6"); + mapKey(KeyCode.F7, "f7"); + mapKey(KeyCode.F8, "f8"); + mapKey(KeyCode.F9, "f9"); + mapKey(KeyCode.F10, "f10"); + mapKey(KeyCode.F11, "f11"); + mapKey(KeyCode.F12, "f12"); + mapKey(KeyCode.NUM0, "0"); + mapKey(KeyCode.NUM1, "1"); + mapKey(KeyCode.NUM2, "2"); + mapKey(KeyCode.NUM3, "3"); + mapKey(KeyCode.NUM4, "4"); + mapKey(KeyCode.NUM5, "5"); + mapKey(KeyCode.NUM6, "6"); + mapKey(KeyCode.NUM7, "7"); + mapKey(KeyCode.NUM8, "8"); + mapKey(KeyCode.NUM9, "9"); + mapKey(KeyCode.SPACEBAR, "spacebar"); + mapKey(KeyCode.A, "a"); + mapKey(KeyCode.B, "b"); + mapKey(KeyCode.C, "c"); + mapKey(KeyCode.D, "d"); + mapKey(KeyCode.E, "e"); + mapKey(KeyCode.F, "f"); + mapKey(KeyCode.G, "g"); + mapKey(KeyCode.H, "h"); + mapKey(KeyCode.I, "i"); + mapKey(KeyCode.J, "j"); + mapKey(KeyCode.K, "k"); + mapKey(KeyCode.L, "l"); + mapKey(KeyCode.M, "m"); + mapKey(KeyCode.N, "n"); + mapKey(KeyCode.O, "o"); + mapKey(KeyCode.P, "p"); + mapKey(KeyCode.Q, "q"); + mapKey(KeyCode.R, "r"); + mapKey(KeyCode.S, "s"); + mapKey(KeyCode.T, "t"); + mapKey(KeyCode.U, "u"); + mapKey(KeyCode.V, "v"); + mapKey(KeyCode.X, "x"); + mapKey(KeyCode.Y, "y"); + mapKey(KeyCode.Z, "z"); + } + + private void mapKey(int keyCode, String presentation) { + keyCodeMap.put(keyCode, presentation); + presentationMap.put(presentation, keyCode); + } + + @Override + public int getKeycodeForString(String attributePresentation) { + Integer code = presentationMap.get(attributePresentation); + return code != null ? code.intValue() : -1; + } + + @Override + public String getStringForKeycode(int keyCode) { + return keyCodeMap.get(keyCode); + } + + }; +}
\ No newline at end of file diff --git a/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java b/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java new file mode 100644 index 0000000000..b8aa6ad3c9 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/DateFieldsTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.design; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +import org.junit.Test; + +import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.ui.DateField; +import com.vaadin.ui.InlineDateField; +import com.vaadin.ui.PopupDateField; +import com.vaadin.ui.declarative.Design; + +/** + * Tests the declarative support for implementations of {@link DateField}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DateFieldsTest { + + @Test + public void testInlineDateFieldToFromDesign() throws Exception { + InlineDateField field = new InlineDateField("Day is", + new SimpleDateFormat("yyyy-MM-dd").parse("2003-02-27")); + field.setResolution(Resolution.DAY); + field.setShowISOWeekNumbers(true); + field.setRangeStart(new SimpleDateFormat("yyyy-MM-dd") + .parse("2001-02-27")); + field.setRangeEnd(new SimpleDateFormat("yyyy-MM-dd") + .parse("2011-02-27")); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Design.write(field, bos); + + InlineDateField result = (InlineDateField) Design + .read(new ByteArrayInputStream(bos.toByteArray())); + assertEquals(field.getResolution(), result.getResolution()); + assertEquals(field.getCaption(), result.getCaption()); + assertEquals(field.getValue(), result.getValue()); + assertEquals(field.getRangeStart(), result.getRangeStart()); + assertEquals(field.getRangeEnd(), result.getRangeEnd()); + } + + @Test + public void testPopupDateFieldFromDesign() throws Exception { + ByteArrayInputStream bis = new ByteArrayInputStream( + "<!DOCTYPE html><html><head></head><body><v-popup-date-field show-iso-week-numbers caption=\"Day is\" resolution=\"MINUTE\" range-end=\"2019-01-15\" input-prompt=\"Pick a day\" value=\"2003-02-27 07:15\"></v-popup-date-field></body></html>" + .getBytes()); + PopupDateField result = (PopupDateField) Design.read(bis); + assertEquals(Resolution.MINUTE, result.getResolution()); + assertEquals("Day is", result.getCaption()); + assertTrue(result.isShowISOWeekNumbers()); + assertEquals("Pick a day", result.getInputPrompt()); + assertEquals( + new SimpleDateFormat("yyyy-MM-dd HH:mm") + .parse("2003-02-27 07:15"), + result.getValue()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2019-01-15"), + result.getRangeEnd()); + + } + + @Test + public void testPopupDateFieldFromDesignInTicket() throws Exception { + ByteArrayInputStream bis = new ByteArrayInputStream( + "<!DOCTYPE html><html><head></head><body><v-date-field range-start=\"2014-05-05\" range-end=\"2014-06-05\" date-out-of-range-message=\"Please select a sensible date\" resolution=\"day\" date-format=\"yyyy-MM-dd\" lenient show-iso-week-numbers parse-error-message=\"You are doing it wrong\" time-zone=\"GMT+5\" value=\"2014-05-15\"></v-date-field></body></html>" + .getBytes()); + DateField result = (DateField) Design.read(bis); + assertEquals(Resolution.DAY, result.getResolution()); + assertTrue(result.isShowISOWeekNumbers()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2014-05-15"), + result.getValue()); + assertEquals(new SimpleDateFormat("yyyy-MM-dd").parse("2014-06-05"), + result.getRangeEnd()); + assertEquals(TimeZone.getTimeZone("GMT+5"), result.getTimeZone()); + } + +} diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java new file mode 100644 index 0000000000..c7909751a1 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.design; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.TimeZone; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.event.ShortcutAction; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.Resource; +import com.vaadin.server.ThemeResource; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.declarative.DesignFormatter; + +/** + * Various tests related to formatter. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DesignFormatterTest { + + private DesignFormatter formatter; + + @Before + public void setUp() { + // initialise with default classes + formatter = new DesignFormatter(); + } + + @Test + public void testSupportedClasses() { + + for (Class<?> type : new Class<?>[] { String.class, Boolean.class, + Integer.class, Float.class, Byte.class, Short.class, + Double.class, ShortcutAction.class, Date.class, + FileResource.class, ExternalResource.class, + ThemeResource.class, Resource.class, TimeZone.class }) { + assertTrue("not supported " + type.getSimpleName(), + formatter.canConvert(type)); + } + } + + @Test + public void testDate() throws Exception { + Date date = new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-17"); + String formatted = formatter.format(date); + Date result = formatter.parse(formatted, Date.class); + + // writing will always give full date string + assertEquals("2012-02-17 00:00:00+0200", formatted); + assertEquals(date, result); + + // try short date as well + result = formatter.parse("2012-02-17", Date.class); + assertEquals(date, result); + } + + @Test + public void testShortcutActions() { + ShortcutAction action = new ShortcutAction("&^d"); + String formatted = formatter.format(action); + // note the space here - it separates key combination from caption + assertEquals("alt-ctrl-d d", formatted); + + ShortcutAction result = formatter + .parse(formatted, ShortcutAction.class); + assertTrue(equals(action, result)); + } + + @Test + public void testShortcutActionNoCaption() { + ShortcutAction action = new ShortcutAction(null, + ShortcutAction.KeyCode.D, new int[] { + ShortcutAction.ModifierKey.ALT, + ShortcutAction.ModifierKey.CTRL }); + String formatted = formatter.format(action); + assertEquals("alt-ctrl-d", formatted); + + ShortcutAction result = formatter + .parse(formatted, ShortcutAction.class); + assertTrue(equals(action, result)); + } + + @Test + public void testTimeZone() { + TimeZone zone = TimeZone.getTimeZone("GMT+2"); + String formatted = formatter.format(zone); + assertEquals("GMT+02:00", formatted); + TimeZone result = formatter.parse(formatted, TimeZone.class); + assertEquals(zone, result); + // try shorthand notation as well + result = formatter.parse("GMT+2", TimeZone.class); + assertEquals(zone, result); + } + + /** + * A static method to allow comparison two different actions. + * + * @param act + * One action to compare. + * @param other + * Second action to compare. + * @return <b>true</b> when both actions are the same (caption, icon, and + * key combination). + */ + public static final boolean equals(ShortcutAction act, ShortcutAction other) { + if (SharedUtil.equals(other.getCaption(), act.getCaption()) + && SharedUtil.equals(other.getIcon(), act.getIcon()) + && act.getKeyCode() == other.getKeyCode() + && act.getModifiers().length == other.getModifiers().length) { + HashSet<Integer> thisSet = new HashSet<Integer>( + act.getModifiers().length); + // this is a bit tricky comparison, but there is no nice way of + // making int[] into a Set + for (int mod : act.getModifiers()) { + thisSet.add(mod); + } + for (int mod : other.getModifiers()) { + thisSet.remove(mod); + } + return thisSet.isEmpty(); + } + return false; + } + +} diff --git a/server/tests/src/com/vaadin/tests/design/InvalidTagNames.java b/server/tests/src/com/vaadin/tests/design/InvalidTagNames.java new file mode 100644 index 0000000000..0b6ccf8cb1 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/InvalidTagNames.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.design; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.declarative.Design; +import com.vaadin.ui.declarative.DesignException; + +public class InvalidTagNames { + + @Test(expected = DesignException.class) + public void tagWithoutDash() { + readDesign("<vbutton>foo</vbutton>"); + } + + @Test + public void emptyTag() { + // JSoup parses empty tags into text nodes + Component c = readDesign("<>foo</>"); + Assert.assertNull(c); + } + + @Test(expected = DesignException.class) + public void onlyPrefix() { + readDesign("<v->foo</v->"); + } + + @Test + public void onlyClass() { + // JSoup will refuse to parse tags starting with - and convert them into + // text nodes instead + Component c = readDesign("<-v>foo</-v>"); + Assert.assertNull(c); + } + + @Test(expected = DesignException.class) + public void unknownClass() { + readDesign("<v-unknownbutton>foo</v-unknownbutton>"); + } + + @Test(expected = DesignException.class) + public void unknownTag() { + readDesign("<x-button></x-button>"); + } + + // @Test(expected = DesignException.class) + // This is a side effect of not actively checking for invalid input. Will be + // parsed currently as <v-button> (this should not be considered API) + public void tagEndsInDash() { + Component c = readDesign("<v-button-></v-button->"); + Assert.assertTrue(c.getClass() == Button.class); + } + + // @Test(expected = DesignException.class) + // This is a side effect of not actively checking for invalid input. Will be + // parsed currently as <v-button> (this should not be considered API) + public void tagEndsInTwoDashes() { + Component c = readDesign("<v-button--></v-button-->"); + Assert.assertTrue(c.getClass() == Button.class); + } + + // @Test(expected = DesignException.class) + // This is a side effect of not actively checking for invalid input. Will be + // parsed currently as <v-button> (this should not be considered API) + public void tagWithTwoDashes() { + Component c = readDesign("<v--button></v--button>"); + Assert.assertTrue(c.getClass() == Button.class); + } + + @Test(expected = DesignException.class) + public void specialCharacters() { + readDesign("<v-button-&!#></v-button-&!#>"); + } + + private Component readDesign(String string) { + try { + return Design.read(new ByteArrayInputStream(string + .getBytes("UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/server/tests/src/com/vaadin/tests/design/LocaleTest.java b/server/tests/src/com/vaadin/tests/design/LocaleTest.java index 939080fbbc..8f0ef4d13e 100644 --- a/server/tests/src/com/vaadin/tests/design/LocaleTest.java +++ b/server/tests/src/com/vaadin/tests/design/LocaleTest.java @@ -15,15 +15,18 @@ */ package com.vaadin.tests.design; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.ByteArrayInputStream; import java.util.Locale; -import junit.framework.TestCase; - import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.junit.Before; +import org.junit.Test; import com.vaadin.ui.Button; import com.vaadin.ui.Component; @@ -32,11 +35,6 @@ import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.declarative.Design; import com.vaadin.ui.declarative.DesignContext; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** * Tests the handling of the locale property in parsing and html generation. diff --git a/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java b/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java new file mode 100644 index 0000000000..9af23b86b1 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/event/ShortcutActionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashSet; + +import org.junit.Test; + +import com.vaadin.event.ShortcutAction; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.tests.design.DesignFormatterTest; + +/** + * Tests various things about shortcut actions. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class ShortcutActionTest { + + private static final String[] KEYS = "a b c d e f g h i j k l m n o p q r s t u v w x y z" + .split("\\s+"); + + @Test + public void testHashCodeUniqueness() { + HashSet<ShortcutAction> set = new HashSet<ShortcutAction>(); + for (String modifier : new String[] { "^", "&", "_", "&^", "&_", "_^", + "&^_" }) { + for (String key : KEYS) { + ShortcutAction action = new ShortcutAction(modifier + key); + for (ShortcutAction other : set) { + assertFalse(equals(action, other)); + } + set.add(action); + } + } + } + + @Test + public void testModifierOrderIrrelevant() { + for (String key : KEYS) { + // two modifiers + for (String modifier : new String[] { "&^", "&_", "_^" }) { + ShortcutAction action1 = new ShortcutAction(modifier + key); + ShortcutAction action2 = new ShortcutAction( + modifier.substring(1) + modifier.substring(0, 1) + key); + assertTrue(modifier + key, equals(action1, action2)); + } + // three modifiers + ShortcutAction action1 = new ShortcutAction("&^_" + key); + for (String modifier : new String[] { "&_^", "^&_", "^_&", "_^&", + "_&^" }) { + ShortcutAction action2 = new ShortcutAction(modifier + key); + assertTrue(modifier + key, equals(action1, action2)); + + } + } + } + + @Test + public void testSameKeycodeDifferentCaptions() { + ShortcutAction act1 = new ShortcutAction("E&xit"); + ShortcutAction act2 = new ShortcutAction("Lu&xtorpeda - Autystyczny"); + assertFalse(equals(act1, act2)); + } + + /** + * A static method to allow comparison two different actions. + * + * @see DesignFormatterTest + * + * @param act + * One action to compare. + * @param other + * Second action to compare. + * @return <b>true</b> when both actions are the same (caption, icon, and + * key combination). + */ + public static final boolean equals(ShortcutAction act, ShortcutAction other) { + if (SharedUtil.equals(other.getCaption(), act.getCaption()) + && SharedUtil.equals(other.getIcon(), act.getIcon()) + && act.getKeyCode() == other.getKeyCode() + && act.getModifiers().length == other.getModifiers().length) { + HashSet<Integer> thisSet = new HashSet<Integer>( + act.getModifiers().length); + // this is a bit tricky comparison, but there is no nice way of + // making int[] into a Set + for (int mod : act.getModifiers()) { + thisSet.add(mod); + } + for (int mod : other.getModifiers()) { + thisSet.remove(mod); + } + return thisSet.isEmpty(); + } + return false; + } + +} diff --git a/shared/src/com/vaadin/shared/ui/ui/Transport.java b/shared/src/com/vaadin/shared/ui/ui/Transport.java index 39174caddf..1aae57e266 100644 --- a/shared/src/com/vaadin/shared/ui/ui/Transport.java +++ b/shared/src/com/vaadin/shared/ui/ui/Transport.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,7 +18,7 @@ package com.vaadin.shared.ui.ui; /** * Transport modes for Push - * + * * @since 7.1 * @author Vaadin Ltd */ @@ -46,6 +46,12 @@ public enum Transport { return identifier; } + /** + * Returns a Transport by its identifier. Returns null if no value is found + * for the given identifier. + * + * @since 7.3.10 + */ public static Transport getByIdentifier(String identifier) { for (Transport t : values()) { if (t.getIdentifier().equals(identifier)) { diff --git a/shared/src/com/vaadin/shared/util/SharedUtil.java b/shared/src/com/vaadin/shared/util/SharedUtil.java index 206041235a..bc5d87b9f5 100644 --- a/shared/src/com/vaadin/shared/util/SharedUtil.java +++ b/shared/src/com/vaadin/shared/util/SharedUtil.java @@ -16,6 +16,7 @@ package com.vaadin.shared.util; import java.io.Serializable; +import java.util.Locale; /** * Misc internal utility methods used by both the server and the client package. @@ -168,7 +169,8 @@ public class SharedUtil implements Serializable { return string.toUpperCase(); } - return string.substring(0, 1).toUpperCase() + string.substring(1); + return string.substring(0, 1).toUpperCase(Locale.ENGLISH) + + string.substring(1); } /** diff --git a/uitest/src/com/vaadin/tests/components/accordion/AccordionClipsContentTest.java b/uitest/src/com/vaadin/tests/components/accordion/AccordionClipsContentTest.java index b4f830d106..25e40e5ed3 100644 --- a/uitest/src/com/vaadin/tests/components/accordion/AccordionClipsContentTest.java +++ b/uitest/src/com/vaadin/tests/components/accordion/AccordionClipsContentTest.java @@ -15,10 +15,9 @@ */ package com.vaadin.tests.components.accordion; -import org.junit.Test; - import com.vaadin.testbench.elements.NativeButtonElement; import com.vaadin.tests.tb3.MultiBrowserTest; +import org.junit.Test; public class AccordionClipsContentTest extends MultiBrowserTest { @Override @@ -48,6 +47,11 @@ public class AccordionClipsContentTest extends MultiBrowserTest { $(NativeButtonElement.class).first().click(); + // Give the button time to pop back up in IE8. + // If this sleep causes issues, next best thing is to click outside the + // button to remove focus - needs new screenshots for all browsers. + Thread.sleep(10); + compareScreen("button-clicked"); } diff --git a/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetails.java b/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetails.java new file mode 100644 index 0000000000..62641aaba4 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetails.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.calendar; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.table.DndTableTargetDetails; +import com.vaadin.ui.Calendar; + +/** + * Test UI for calendar as a drop target: CalendarTargetDetails should provide + * getMouseEvent() method. + * + * @author Vaadin Ltd + */ +public class DndCalendarTargetDetails extends DndTableTargetDetails { + + @Override + protected void setup(VaadinRequest request) { + createSourceTable(); + + Calendar calendar = new Calendar(); + calendar.addStyleName("target"); + calendar.setDropHandler(new TestDropHandler()); + calendar.setWidth(100, Unit.PERCENTAGE); + addComponent(calendar); + } + + @Override + protected String getTestDescription() { + return "Mouse details should be available for CalendarTargetDetails DnD when calendar is a target"; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetailsTest.java b/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetailsTest.java new file mode 100644 index 0000000000..c90d541226 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/calendar/DndCalendarTargetDetailsTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.calendar; + +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.By; +import com.vaadin.tests.components.table.DndTableTargetDetailsTest; + +/** + * Test for mouse details in CalendarTargetDetails class when DnD target is a + * calendar. + * + * @author Vaadin Ltd + */ +public class DndCalendarTargetDetailsTest extends DndTableTargetDetailsTest { + + @Override + protected WebElement getTarget() { + return findElement(By.className("v-datecellslot-even")); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/WidgetRenderersTest.java b/uitest/src/com/vaadin/tests/components/grid/WidgetRenderersTest.java index 5da92b2034..854d7fe3f2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/WidgetRenderersTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/WidgetRenderersTest.java @@ -15,15 +15,6 @@ */ package com.vaadin.tests.components.grid; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.interactions.Actions; - import com.vaadin.testbench.By; import com.vaadin.testbench.elements.ButtonElement; import com.vaadin.testbench.elements.GridElement; @@ -31,6 +22,15 @@ import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.annotations.TestCategory; import com.vaadin.tests.tb3.MultiBrowserTest; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; /** * TB tests for the various builtin widget-based renderers. @@ -41,30 +41,31 @@ import com.vaadin.tests.tb3.MultiBrowserTest; @TestCategory("grid") public class WidgetRenderersTest extends MultiBrowserTest { - @Test - public void testProgressBarRenderer() { + @Override + public void setup() throws Exception { + super.setup(); + openTestURL(); + } + @Test + public void testProgressBarRenderer() { assertTrue(getGridCell(0, 0).isElementPresent( By.className("v-progressbar"))); } @Test public void testButtonRenderer() { - openTestURL(); - WebElement button = getGridCell(0, 1).findElement( By.className("v-nativebutton")); button.click(); - assertEquals("Clicked!", button.getText()); + waitUntilTextUpdated(button, "Clicked!"); } @Test public void testButtonRendererAfterCellBeingFocused() { - openTestURL(); - GridCellElement buttonCell = getGridCell(0, 1); assertFalse("cell should not be focused before focusing", buttonCell.isFocused()); @@ -80,30 +81,41 @@ public class WidgetRenderersTest extends MultiBrowserTest { "Clicked!", button.getText()); new Actions(getDriver()).moveToElement(button).click().perform(); - assertEquals("Button should be clicked after click", "Clicked!", - button.getText()); + + waitUntilTextUpdated(button, "Clicked!"); } @Test public void testImageRenderer() { - openTestURL(); - - WebElement image = getGridCell(0, 2).findElement( + final WebElement image = getGridCell(0, 2).findElement( By.className("gwt-Image")); - assertTrue(image.getAttribute("src").endsWith("window/img/close.png")); + waitUntilmageSrcEndsWith(image, "window/img/close.png"); image.click(); - assertTrue(image.getAttribute("src") - .endsWith("window/img/maximize.png")); + waitUntilmageSrcEndsWith(image, "window/img/maximize.png"); + } + + private void waitUntilmageSrcEndsWith(final WebElement image, final String expectedText) { + waitUntil(new ExpectedCondition<Boolean>() { + + @Override + public Boolean apply(WebDriver input) { + return image.getAttribute("src").endsWith(expectedText); + } + + @Override + public String toString() { + // Timed out after 10 seconds waiting for ... + return String.format("image source to update. Supposed to end with '%s' (was: '%s').", + expectedText, image.getAttribute("src")); + } + }); } @Test public void testColumnReorder() { - setDebug(true); - openTestURL(); - $(ButtonElement.class).caption("Change column order").first().click(); assertFalse("Notification was present", @@ -119,14 +131,34 @@ public class WidgetRenderersTest extends MultiBrowserTest { @Test public void testPropertyIdInEvent() { - openTestURL(); WebElement button = getGridCell(0, 3).findElement( By.className("v-nativebutton")); + button.click(); - assertEquals(WidgetRenderers.PROPERTY_ID, button.getText()); + + waitUntilTextUpdated(button, WidgetRenderers.PROPERTY_ID); } GridCellElement getGridCell(int row, int col) { return $(GridElement.class).first().getCell(row, col); } + + private void waitUntilTextUpdated(final WebElement button, + final String expectedText) { + waitUntil(new ExpectedCondition<Boolean>() { + + @Override + public Boolean apply(WebDriver input) { + return button.getText().equals(expectedText); + } + + @Override + public String toString() { + // Timed out after 10 seconds waiting for ... + return String.format("button's text to become '%s' (was: '').", + expectedText, button.getText()); + } + + }); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java index b122eb02e9..f251313100 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java @@ -18,7 +18,9 @@ package com.vaadin.tests.components.grid.basicfeatures.server; import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; import com.vaadin.testbench.elements.GridElement; @@ -44,11 +46,8 @@ public class LoadingIndicatorTest extends GridBasicFeaturesTest { gridElement.getCell(200, 1); // Wait for loading indicator delay - Thread.sleep(500); - - Assert.assertTrue( - "Loading indicator should be visible when fetching rows that are visible", - isLoadingIndicatorVisible()); + waitUntil(ExpectedConditions.visibilityOfElementLocated(By + .className("v-loading-indicator"))); waitUntilNot(ExpectedConditions.visibilityOfElementLocated(By .className("v-loading-indicator"))); @@ -65,22 +64,29 @@ public class LoadingIndicatorTest extends GridBasicFeaturesTest { isLoadingIndicatorVisible()); // Finally verify that there was actually a request going on - Thread.sleep(2000); + waitUntilLogContains("Requested items"); + } - String firstLogRow = getLogRow(0); - Assert.assertTrue( - "Last log message should be number 6: " + firstLogRow, - firstLogRow.startsWith("6. Requested items")); + private void waitUntilLogContains(final String value) { + waitUntil(new ExpectedCondition<Boolean>() { + @Override + public Boolean apply(WebDriver input) { + return getLogRow(0).contains(value); + } + + @Override + public String toString() { + // Timed out after 10 seconds waiting for ... + return "first log row to contain '" + value + "' (was: '" + + getLogRow(0) + "')"; + } + }); } private boolean isLoadingIndicatorVisible() { WebElement loadingIndicator = findElement(By .className("v-loading-indicator")); - if (loadingIndicator == null) { - return false; - } else { - return loadingIndicator.isDisplayed(); - } + return loadingIndicator.isDisplayed(); } } diff --git a/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetails.java b/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetails.java new file mode 100644 index 0000000000..7bebfafd4e --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetails.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.table; + +import com.vaadin.data.util.BeanItemContainer; +import com.vaadin.event.dd.DragAndDropEvent; +import com.vaadin.event.dd.DropHandler; +import com.vaadin.event.dd.TargetDetailsImpl; +import com.vaadin.event.dd.acceptcriteria.AcceptAll; +import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Label; +import com.vaadin.ui.Table; +import com.vaadin.ui.Table.TableDragMode; +import com.vaadin.ui.VerticalLayout; + +/** + * Test UI for table as a drop target: AbstractSelectTargetDetails should + * provide getMouseEvent() method. + * + * @author Vaadin Ltd + */ +public class DndTableTargetDetails extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + createSourceTable(); + + Table target = new Table(); + BeanItemContainer<TestBean> container = new BeanItemContainer<TestBean>( + TestBean.class); + container.addBean(new TestBean("target-item")); + target.setContainerDataSource(container); + target.setPageLength(1); + target.addStyleName("target"); + target.setWidth(100, Unit.PERCENTAGE); + target.setDropHandler(new TestDropHandler()); + addComponent(target); + } + + protected void createSourceTable() { + Table table = new Table(); + table.setPageLength(1); + table.setDragMode(TableDragMode.ROW); + table.setWidth(100, Unit.PERCENTAGE); + BeanItemContainer<TestBean> container = new BeanItemContainer<TestBean>( + TestBean.class); + container.addBean(new TestBean("item")); + table.setContainerDataSource(container); + addComponent(table); + } + + @Override + protected String getTestDescription() { + return "Mouse details should be available for AbstractSelectTargetDetails DnD when table is a target"; + } + + @Override + protected Integer getTicketNumber() { + return 13416; + } + + protected static class TestDropHandler implements DropHandler { + + public TestDropHandler() { + } + + @Override + public void drop(DragAndDropEvent event) { + TargetDetailsImpl details = (TargetDetailsImpl) event + .getTargetDetails(); + MouseEventDetails mouseDetails = details.getMouseEvent(); + + VerticalLayout layout = (VerticalLayout) details.getTarget() + .getUI().getContent(); + + Label name = new Label("Button name=" + + mouseDetails.getButtonName()); + name.addStyleName("dnd-button-name"); + layout.addComponent(name); + if (mouseDetails.isCtrlKey()) { + name.addStyleName("ctrl"); + } + if (mouseDetails.isAltKey()) { + name.addStyleName("alt"); + } + if (mouseDetails.isShiftKey()) { + name.addStyleName("shift"); + } + + layout.addComponent(name); + } + + @Override + public AcceptCriterion getAcceptCriterion() { + return AcceptAll.get(); + } + + } + + public static class TestBean { + private String name; + + public TestBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + +} diff --git a/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetailsTest.java b/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetailsTest.java new file mode 100644 index 0000000000..5ad99d3084 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/DndTableTargetDetailsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.table; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.By; +import com.vaadin.tests.tb3.DndActionsTest; + +/** + * Test for mouse details in AbstractSelectTargetDetails class when DnD target + * is a table. + * + * @author Vaadin Ltd + */ +public class DndTableTargetDetailsTest extends DndActionsTest { + + @Test + public void testMouseDetails() throws IOException, InterruptedException { + openTestURL(); + + WebElement row = findElement(By.className("v-table-cell-wrapper")); + + dragAndDrop(row, getTarget()); + + WebElement label = findElement(By.className("dnd-button-name")); + Assert.assertEquals("Button name=left", label.getText()); + } + + protected WebElement getTarget() { + return findElement(By.className("target")).findElement( + By.className("v-table-cell-wrapper")); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetails.java b/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetails.java new file mode 100644 index 0000000000..443f290809 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetails.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.tree; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.table.DndTableTargetDetails; +import com.vaadin.ui.Tree; + +/** + * Test UI for tree as a drop target: AbstractSelectTargetDetails should provide + * getMouseEvent() method. + * + * @author Vaadin Ltd + */ +public class DndTreeTargetDetails extends DndTableTargetDetails { + + @Override + protected void setup(VaadinRequest request) { + createSourceTable(); + + Tree target = new Tree(); + target.addStyleName("target"); + target.setWidth(100, Unit.PERCENTAGE); + target.addItem("treeItem"); + target.setDropHandler(new TestDropHandler()); + addComponent(target); + } + + @Override + protected String getTestDescription() { + return "Mouse details should be available for AbstractSelectTargetDetails DnD when tree is a target"; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetailsTest.java b/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetailsTest.java new file mode 100644 index 0000000000..c1b77df5e9 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tree/DndTreeTargetDetailsTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.components.tree; + +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.By; +import com.vaadin.tests.components.table.DndTableTargetDetailsTest; + +/** + * Test for mouse details in AbstractSelectTargetDetails class when DnD target + * is a tree. + * + * @author Vaadin Ltd + */ +public class DndTreeTargetDetailsTest extends DndTableTargetDetailsTest { + + @Override + protected WebElement getTarget() { + return findElement(By.className("target")); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNone.java b/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNone.java new file mode 100644 index 0000000000..dfea493281 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNone.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.tree; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Tree; + +/** + * Test UI for keyboard navigation for first and last tree item. + * + * @author Vaadin Ltd + */ +public class TreeKeyboardNavigationToNone extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + final Tree tree = new Tree(); + tree.addItem("a"); + tree.addItem("b"); + + tree.select("a"); + addComponents(tree); + tree.focus(); + + Button button = new Button("Select last item", + new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + tree.select("b"); + tree.focus(); + } + }); + addComponent(button); + } + + @Override + protected Integer getTicketNumber() { + return 15343; + } + + @Override + protected String getTestDescription() { + return "Keyboard navigation should not throw client side exception " + + "when there are no items to navigate."; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNoneTest.java b/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNoneTest.java new file mode 100644 index 0000000000..98f1896b82 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tree/TreeKeyboardNavigationToNoneTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.tree; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * Test for keyboard navigation in tree in case when there are no items to + * navigate. + * + * @author Vaadin Ltd + */ +public class TreeKeyboardNavigationToNoneTest extends MultiBrowserTest { + + @Before + public void setUp() { + setDebug(true); + openTestURL(); + } + + @Test + public void navigateUpForTheFirstItem() { + sendKey(Keys.ARROW_UP); + checkNotificationErrorAbsence("first"); + } + + @Test + public void navigateDownForTheLastItem() { + $(ButtonElement.class).first().click(); + sendKey(Keys.ARROW_DOWN); + checkNotificationErrorAbsence("last"); + } + + private void checkNotificationErrorAbsence(String item) { + Assert.assertFalse( + "Notification is found after using keyboard for navigation " + + "from " + item + " tree item", + isElementPresent(By.className("v-Notification"))); + } + + private void sendKey(Keys key) { + Actions actions = new Actions(getDriver()); + actions.sendKeys(key).build().perform(); + } +} diff --git a/uitest/src/com/vaadin/tests/components/window/CloseShortcut.java b/uitest/src/com/vaadin/tests/components/window/CloseShortcut.java new file mode 100644 index 0000000000..c7499134e0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/CloseShortcut.java @@ -0,0 +1,121 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.window; + +import com.vaadin.data.Property; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +/** + * Tests close shortcuts for Window. + * + * @author Vaadin Ltd + */ +public class CloseShortcut extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + final Window w = new Window(); + w.setWidth("300px"); + w.setHeight("300px"); + w.center(); + addWindow(w); + w.focus(); + + // add textfield to the window to give TestBench something to send the + // keys to + VerticalLayout content = new VerticalLayout(); + content.setMargin(true); + TextField textField = new TextField(); + textField.setSizeFull(); + content.addComponent(textField); + w.setContent(content); + + final CheckBox cbDefault = new CheckBox("Use default (ESC) shortcut"); + cbDefault.setId("default"); + addComponent(cbDefault); + final CheckBox cbOther = new CheckBox("Use R shortcut"); + cbOther.setId("other"); + addComponent(cbOther); + final CheckBox cbCtrl = new CheckBox("Use CTRL+A shortcut"); + cbCtrl.setId("control"); + addComponent(cbCtrl); + final CheckBox cbShift = new CheckBox("Use SHIFT+H shortcut"); + cbShift.setId("shift"); + addComponent(cbShift); + + cbOther.setValue(true); + cbCtrl.setValue(true); + cbShift.setValue(true); + + Property.ValueChangeListener listener = new Property.ValueChangeListener() { + + @Override + public void valueChange(ValueChangeEvent event) { + if (Boolean.TRUE.equals(cbDefault.getValue())) { + w.resetCloseShortcuts(); + } else { + w.removeCloseShortcuts(); + } + if (Boolean.TRUE.equals(cbOther.getValue())) { + w.addCloseShortcut(KeyCode.R); + } + if (Boolean.TRUE.equals(cbCtrl.getValue())) { + w.addCloseShortcut(KeyCode.A, ModifierKey.CTRL); + } + if (Boolean.TRUE.equals(cbShift.getValue())) { + w.addCloseShortcut(KeyCode.H, ModifierKey.SHIFT); + } + } + }; + cbDefault.addValueChangeListener(listener); + cbOther.addValueChangeListener(listener); + cbCtrl.addValueChangeListener(listener); + cbShift.addValueChangeListener(listener); + + cbDefault.setValue(true); // trigger value change + + Button button = new Button("Reopen window", new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + w.close(); + addWindow(w); + w.focus(); + } + }); + addComponent(button); + } + + @Override + protected String getTestDescription() { + return "It should be possible to have multiple shortcuts at the same time, and to remove the default shortcut ESC."; + } + + @Override + protected Integer getTicketNumber() { + return 14843; + } +} diff --git a/uitest/src/com/vaadin/tests/components/window/CloseShortcutTest.java b/uitest/src/com/vaadin/tests/components/window/CloseShortcutTest.java new file mode 100644 index 0000000000..7cca2ce4d0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/CloseShortcutTest.java @@ -0,0 +1,222 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.window; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import com.vaadin.testbench.elements.CheckBoxElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.testbench.elements.WindowElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * Tests close shortcuts for Window. + * + * @author Vaadin Ltd + */ +public class CloseShortcutTest extends MultiBrowserTest { + + private WindowElement window; + private CheckBoxElement cbDefault; + private CheckBoxElement cbOther; + private CheckBoxElement cbCtrl; + private CheckBoxElement cbShift; + + @Override + @Before + public void setup() throws Exception { + super.setup(); + openTestURL(); + waitForElementPresent(By.className("v-window")); + + window = $(WindowElement.class).first(); + cbDefault = $(CheckBoxElement.class).id("default"); + cbOther = $(CheckBoxElement.class).id("other"); + cbCtrl = $(CheckBoxElement.class).id("control"); + cbShift = $(CheckBoxElement.class).id("shift"); + } + + @Test + public void testAllCheckBoxesSelected() { + assertTrue("Default wasn't selected initially.", isChecked(cbDefault)); + assertTrue("Other wasn't selected initially.", isChecked(cbOther)); + assertTrue("Ctrl+A wasn't selected initially.", isChecked(cbCtrl)); + assertTrue("Shift+H wasn't selected initially.", isChecked(cbShift)); + } + + @Test + public void testAllCheckBoxesClickable() { + click(cbDefault); + click(cbOther); + click(cbCtrl); + click(cbShift); + + assertFalse("Default was selected when it shouldn't have been.", + isChecked(cbDefault)); + assertFalse("Other was selected when it shouldn't have been.", + isChecked(cbOther)); + assertFalse("Ctrl+A was selected when it shouldn't have been.", + isChecked(cbCtrl)); + assertFalse("Shift+H was selected when it shouldn't have been.", + isChecked(cbShift)); + } + + @Test + public void testDefaultWithAll() { + attemptDefaultShortcut(); + ensureWindowClosed(); + } + + @Test + public void testDefaultWithoutSelection() { + click(cbDefault); + + attemptDefaultShortcut(); + ensureWindowOpen(); + } + + @Test + public void testOtherWithAll() { + attemptOtherShortcut(); + // TODO: remove this check once #14902 has been fixed + if (!Browser.IE8.getDesiredCapabilities().equals( + getDesiredCapabilities()) + && !Browser.FIREFOX.getDesiredCapabilities().equals( + getDesiredCapabilities()) + && !Browser.CHROME.getDesiredCapabilities().equals( + getDesiredCapabilities())) { + ensureWindowClosed(); + } + } + + @Test + public void testOtherWithoutSelection() { + click(cbOther); + + attemptOtherShortcut(); + ensureWindowOpen(); + } + + @Test + public void testCtrlWithAll() { + attemptCtrlShortcut(); + // TODO: remove this check once #14902 has been fixed + if (Browser.PHANTOMJS.getDesiredCapabilities().equals( + getDesiredCapabilities())) { + ensureWindowClosed(); + } + } + + @Test + public void testCtrlWithoutSelection() { + click(cbCtrl); + + attemptCtrlShortcut(); + ensureWindowOpen(); + } + + @Test + public void testShiftWithAll() { + attemptShiftShortcut(); + // TODO: remove this check once #14902 has been fixed + if (getBrowsersExcludingIE().contains(getDesiredCapabilities()) + || Browser.IE8.getDesiredCapabilities().equals( + getDesiredCapabilities())) { + ensureWindowClosed(); + } + } + + @Test + public void testShiftWithoutSelection() { + click(cbShift); + + attemptShiftShortcut(); + ensureWindowOpen(); + } + + private boolean isChecked(CheckBoxElement cb) { + String checked = cb.findElement(By.tagName("input")).getAttribute( + "checked"); + if ("true".equals(checked)) { + return true; + } else if (checked == null) { + return false; + } + throw new IllegalStateException( + "Unexpected attribute value for 'checked': " + checked); + } + + @Override + protected void click(final CheckBoxElement cb) { + final boolean initial = isChecked(cb); + super.click(cb); + waitUntil(new ExpectedCondition<Boolean>() { + @Override + public Boolean apply(WebDriver input) { + return initial != isChecked(cb); + } + + @Override + public String toString() { + // Timed out after 10 seconds waiting for ... + return "checked state to change"; + } + }); + } + + private void attemptDefaultShortcut() { + window.focus(); + $(TextFieldElement.class).first().sendKeys(Keys.ESCAPE); + } + + private void attemptOtherShortcut() { + window.focus(); + $(TextFieldElement.class).first().sendKeys("R"); + } + + private void attemptCtrlShortcut() { + window.focus(); + new Actions(driver).keyDown(Keys.CONTROL).perform(); + $(TextFieldElement.class).first().sendKeys("A"); + new Actions(driver).keyUp(Keys.CONTROL).perform(); + } + + private void attemptShiftShortcut() { + window.focus(); + new Actions(driver).keyDown(Keys.SHIFT).perform(); + $(TextFieldElement.class).first().sendKeys("H"); + new Actions(driver).keyUp(Keys.SHIFT).perform(); + } + + private void ensureWindowClosed() { + assertTrue("Window didn't close as expected.", $(WindowElement.class) + .all().isEmpty()); + } + + private void ensureWindowOpen() { + assertFalse("Window closed when it shouldn't have.", + $(WindowElement.class).all().isEmpty()); + } +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java b/uitest/src/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java new file mode 100644 index 0000000000..30cc5676bb --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java @@ -0,0 +1,191 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +import java.util.Iterator; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.fieldgroup.BeanFieldGroup; +import com.vaadin.data.fieldgroup.FieldGroup.CommitException; +import com.vaadin.data.fieldgroup.PropertyId; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.BeanItemContainer; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Field; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.TextField; + +@Theme("valo") +public abstract class AbstractBasicCrud extends AbstractTestUIWithLog { + + protected AbstractForm form; + protected static String[] columns = new String[] { "firstName", "lastName", + "gender", "birthDate", "age", "alive", "address.streetAddress", + "address.postalCode", "address.city", "address.country" }; + protected BeanItemContainer<ComplexPerson> container = ComplexPerson + .createContainer(100);; + { + container.addNestedContainerBean("address"); + } + protected ComboBox formType; + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#setup(com.vaadin.server. + * VaadinRequest) + */ + @Override + protected void setup(VaadinRequest request) { + getLayout().setSizeFull(); + getLayout().setSpacing(true); + getContent().setSizeFull(); + form = new CustomForm(); + + formType = new ComboBox(); + formType.setNullSelectionAllowed(false); + formType.setWidth("300px"); + formType.addItem(form); + formType.setValue(form); + formType.addItem(new AutoGeneratedForm(TextField.class)); + formType.addItem(new AutoGeneratedForm(Field.class)); + Iterator<?> iterator = formType.getItemIds().iterator(); + formType.setItemCaption(iterator.next(), "TextField based form"); + formType.setItemCaption(iterator.next(), + "Auto generated form (TextFields)"); + formType.setItemCaption(iterator.next(), + "Auto generated form (Any fields)"); + formType.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(ValueChangeEvent event) { + AbstractForm oldForm = form; + form = (AbstractForm) formType.getValue(); + replaceComponent(oldForm, form); + } + }); + + addComponent(formType); + + } + + public class CustomForm extends AbstractForm { + + private TextField firstName = new TextField("First name"); + private TextField lastName = new TextField("Last name"); + private TextField gender = new TextField("Gender"); + private TextField birthDate = new TextField("Birth date"); + private TextField age = new TextField("Age"); + private CheckBox alive = new CheckBox("Alive"); + + @PropertyId("address.streetAddress") + private TextField address_streetAddress = new TextField( + "Street address"); + @PropertyId("address.postalCode") + private TextField address_postalCode = new TextField("Postal code"); + @PropertyId("address.city") + private TextField address_city = new TextField("City"); + @PropertyId("address.country") + private TextField address_country = new TextField("Country"); + + public CustomForm() { + fieldGroup.bindMemberFields(this); + + address_postalCode.setNullRepresentation(""); + gender.setNullRepresentation(""); + age.setNullRepresentation(""); + address_country.setNullRepresentation(""); + birthDate.setNullRepresentation(""); + + setDefaultComponentAlignment(Alignment.MIDDLE_LEFT); + addComponents(firstName, lastName, gender, birthDate, age, alive, + address_streetAddress, address_postalCode, address_city, + address_country); + + HorizontalLayout hl = new HorizontalLayout(save, cancel); + hl.setSpacing(true); + addComponent(hl); + + } + + } + + protected abstract void deselectAll(); + + public class AbstractForm extends GridLayout { + protected Button save = new Button("Save"); + protected Button cancel = new Button("Cancel"); + + protected BeanFieldGroup<ComplexPerson> fieldGroup = new BeanFieldGroup<ComplexPerson>( + ComplexPerson.class); + + public AbstractForm() { + super(5, 1); + setSpacing(true); + setId("form"); + save.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + fieldGroup.commit(); + log("Saved " + fieldGroup.getItemDataSource()); + } catch (CommitException e) { + log("Commit failed: " + e.getMessage()); + } + } + }); + cancel.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + log("Discarded " + fieldGroup.getItemDataSource()); + deselectAll(); + } + }); + } + + public void edit(BeanItem<ComplexPerson> item) { + fieldGroup.setItemDataSource(item); + } + } + + public class AutoGeneratedForm extends AbstractForm { + + public AutoGeneratedForm(Class<? extends Field> class1) { + for (String p : columns) { + Field f = fieldGroup.getFieldFactory().createField( + container.getType(p), class1); + f.setCaption(SharedUtil.propertyIdToHumanFriendly(p)); + fieldGroup.bind(f, p); + addComponent(f); + } + + HorizontalLayout hl = new HorizontalLayout(save, cancel); + hl.setSpacing(true); + addComponent(hl); + } + + } +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrud.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrud.java deleted file mode 100644 index be0368f4ae..0000000000 --- a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrud.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * 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.vaadin.tests.fieldgroup; - -import com.vaadin.annotations.Theme; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; -import com.vaadin.data.fieldgroup.BeanFieldGroup; -import com.vaadin.data.fieldgroup.PropertyId; -import com.vaadin.server.VaadinRequest; -import com.vaadin.tests.util.Person; -import com.vaadin.tests.util.PersonContainer; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Table; -import com.vaadin.ui.TextField; -import com.vaadin.ui.UI; -import com.vaadin.ui.VerticalLayout; - -@Theme("valo") -public class BasicCrud extends UI { - - private Form form; - - @Override - protected void init(VaadinRequest request) { - VerticalLayout main = new VerticalLayout(); - main.setMargin(true); - main.setSpacing(true); - final Table t = new Table(); - // t.setSelectionMode(SelectionMode.SINGLE); - t.setSelectable(true); - - PersonContainer c = PersonContainer.createWithTestData(); - - c.addBean(new Person("first", "Last", "email", "phone", "street", - 12332, "Turku")); - c.addBean(new Person("Foo", "Bar", "me@some.where", "123", - "homestreet 12", 10000, "Glasgow")); - t.setContainerDataSource(c); - // t.removeColumn("address"); - // t.setColumnOrder("firstName", "lastName", "email", "phoneNumber", - // "address.streetAddress", "address.postalCode", "address.city"); - t.setVisibleColumns("firstName", "lastName", "email", "phoneNumber", - "address.streetAddress", "address.postalCode", "address.city"); - - // t.addSelectionChangeListener(new SelectionChangeListener() { - // @Override - // public void selectionChange(SelectionChangeEvent event) { - // form.edit((Person) t.getSelectedRow()); - // } - // }); - t.addValueChangeListener(new ValueChangeListener() { - - @Override - public void valueChange(ValueChangeEvent event) { - form.edit((Person) t.getValue()); - } - }); - - form = new Form(); - - t.setSizeFull(); - - main.setSizeFull(); - main.addComponent(t); - main.addComponent(form); - main.setExpandRatio(t, 1); - setContent(main); - } - - public static class Form extends HorizontalLayout { - private TextField firstName = new TextField("First name"); - private TextField lastName = new TextField("Last name"); - private TextField email = new TextField("E-mail"); - @PropertyId("address.streetAddress") - private TextField streetAddress = new TextField("Street address"); - @PropertyId("address.postalCode") - private TextField postalCode = new TextField("Postal code"); - - BeanFieldGroup<Person> fieldGroup = new BeanFieldGroup<Person>( - Person.class); - - public Form() { - setSpacing(true); - setId("form"); - fieldGroup.bindMemberFields(this); - - // Stupid integer binding - postalCode.setNullRepresentation(""); - addComponents(firstName, lastName, email, streetAddress, postalCode); - } - - public void edit(Person p) { - fieldGroup.setItemDataSource(p); - } - } -} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGrid.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGrid.java new file mode 100644 index 0000000000..d7f66b0cd9 --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGrid.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.event.SelectionEvent; +import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Grid; + +public class BasicCrudGrid extends AbstractBasicCrud { + + private Grid grid; + + @Override + protected void setup(VaadinRequest request) { + super.setup(request); + grid = new Grid(); + + grid.setContainerDataSource(container); + + grid.setColumnOrder((Object[]) columns); + grid.removeColumn("salary"); + grid.addSelectionListener(new SelectionListener() { + + @Override + public void select(SelectionEvent event) { + Item item = grid.getContainerDataSource().getItem( + grid.getSelectedRow()); + form.edit((BeanItem<ComplexPerson>) item); + } + }); + + grid.setSizeFull(); + + addComponent(grid); + addComponent(form); + getLayout().setExpandRatio(grid, 1); + } + + @Override + protected void deselectAll() { + grid.select(null); + } + +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGridEditorRow.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGridEditorRow.java new file mode 100644 index 0000000000..63ba2986e1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudGridEditorRow.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.validator.IntegerRangeValidator; +import com.vaadin.event.SelectionEvent; +import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Grid; + +public class BasicCrudGridEditorRow extends AbstractBasicCrud { + + private Grid grid; + + @Override + protected void setup(VaadinRequest request) { + super.setup(request); + formType.setVisible(false); + grid = new Grid(); + + grid.setContainerDataSource(container); + + grid.setColumnOrder((Object[]) columns); + grid.removeColumn("salary"); + grid.addSelectionListener(new SelectionListener() { + @Override + public void select(SelectionEvent event) { + Item item = grid.getContainerDataSource().getItem( + grid.getSelectedRow()); + form.edit((BeanItem<ComplexPerson>) item); + } + }); + grid.setEditorEnabled(true); + grid.setSizeFull(); + grid.getEditorField("age").addValidator( + new IntegerRangeValidator("Must be between 0 and 100", 0, 100)); + addComponent(grid); + getLayout().setExpandRatio(grid, 1); + } + + @Override + protected void deselectAll() { + grid.select(null); + } + +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTable.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTable.java new file mode 100644 index 0000000000..ad54cd55ba --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTable.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.BeanItem; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Table; + +public class BasicCrudTable extends AbstractBasicCrud { + + private Table table; + + @Override + protected void setup(VaadinRequest request) { + super.setup(request); + + table = new Table(); + table.setSelectable(true); + + table.setContainerDataSource(container); + + table.setVisibleColumns((Object[]) columns); + table.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(ValueChangeEvent event) { + form.edit((BeanItem<ComplexPerson>) table.getItem(table + .getValue())); + } + }); + + table.setSizeFull(); + + addComponent(table); + addComponent(form); + getLayout().setExpandRatio(table, 1); + } + + @Override + protected void deselectAll() { + table.setValue(null); + + } + +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTest.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTableTest.java index a41725dbc9..86441b09f9 100644 --- a/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTest.java +++ b/uitest/src/com/vaadin/tests/fieldgroup/BasicCrudTableTest.java @@ -20,19 +20,18 @@ import java.util.List; import org.junit.Assert; import org.junit.Test; -import com.vaadin.testbench.elements.AbstractOrderedLayoutElement; +import com.vaadin.testbench.AbstractHasTestBenchCommandExecutor; +import com.vaadin.testbench.elements.AbstractComponentElement; import com.vaadin.testbench.elements.TableElement; import com.vaadin.testbench.elements.TextFieldElement; import com.vaadin.tests.tb3.SingleBrowserTest; -public class BasicCrudTest extends SingleBrowserTest { +public class BasicCrudTableTest extends SingleBrowserTest { @Test public void fieldsInitiallyEmpty() { openTestURL(); - AbstractOrderedLayoutElement fieldsLayout = $( - AbstractOrderedLayoutElement.class).id("form"); - List<TextFieldElement> textFields = fieldsLayout.$( + List<TextFieldElement> textFields = getFieldsLayout().$( TextFieldElement.class).all(); for (TextFieldElement e : textFields) { @@ -40,16 +39,18 @@ public class BasicCrudTest extends SingleBrowserTest { } } + private AbstractHasTestBenchCommandExecutor getFieldsLayout() { + return $(AbstractComponentElement.class).id("form"); + } + @Test public void fieldsClearedOnDeselect() { openTestURL(); - AbstractOrderedLayoutElement fieldsLayout = $( - AbstractOrderedLayoutElement.class).id("form"); // Select row $(TableElement.class).first().getCell(2, 2).click(); - List<TextFieldElement> textFields = fieldsLayout.$( + List<TextFieldElement> textFields = getFieldsLayout().$( TextFieldElement.class).all(); for (TextFieldElement e : textFields) { diff --git a/uitest/src/com/vaadin/tests/fieldgroup/ComplexAddress.java b/uitest/src/com/vaadin/tests/fieldgroup/ComplexAddress.java new file mode 100644 index 0000000000..d6d657379a --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/ComplexAddress.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +import java.util.Random; + +import com.vaadin.tests.util.TestDataGenerator; + +public class ComplexAddress { + + private String streetAddress = ""; + private String postalCode = ""; + private String city = ""; + private Country country = null; + + public String getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(String streetAddress) { + this.streetAddress = streetAddress; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + public static ComplexAddress create(Random r) { + ComplexAddress ca = new ComplexAddress(); + ca.setCity(TestDataGenerator.getCity(r)); + ca.setCountry(TestDataGenerator.getEnum(Country.class, r)); + ca.setPostalCode(TestDataGenerator.getPostalCode(r) + ""); + ca.setStreetAddress(TestDataGenerator.getStreetAddress(r)); + return ca; + } + +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/ComplexPerson.java b/uitest/src/com/vaadin/tests/fieldgroup/ComplexPerson.java new file mode 100644 index 0000000000..2fb7c2ac04 --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/ComplexPerson.java @@ -0,0 +1,110 @@ +package com.vaadin.tests.fieldgroup; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Random; + +import com.vaadin.data.util.BeanItemContainer; +import com.vaadin.tests.util.TestDataGenerator; + +public class ComplexPerson { + + private String firstName, lastName; + private Integer age; + private Date birthDate; + private BigDecimal salary; + private boolean alive; + private Gender gender; + private ComplexAddress address; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Date getBirthDate() { + return birthDate; + } + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public BigDecimal getSalary() { + return salary; + } + + public void setSalary(BigDecimal salary) { + this.salary = salary; + } + + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + public ComplexAddress getAddress() { + return address; + } + + public void setAddress(ComplexAddress address) { + this.address = address; + } + + public static BeanItemContainer<ComplexPerson> createContainer(int size) { + BeanItemContainer<ComplexPerson> bic = new BeanItemContainer<ComplexPerson>( + ComplexPerson.class); + Random r = new Random(size); + + for (int i = 0; i < size; i++) { + ComplexPerson cp = ComplexPerson.create(r); + bic.addBean(cp); + } + + return bic; + } + + public static ComplexPerson create(Random r) { + ComplexPerson cp = new ComplexPerson(); + cp.setFirstName(TestDataGenerator.getFirstName(r)); + cp.setLastName(TestDataGenerator.getLastName(r)); + cp.setAlive(r.nextBoolean()); + cp.setBirthDate(TestDataGenerator.getBirthDate(r)); + cp.setAge((int) ((new Date(2014 - 1900, 1, 1).getTime() - cp + .getBirthDate().getTime()) / 1000 / 3600 / 24 / 365)); + cp.setSalary(TestDataGenerator.getSalary(r)); + cp.setAddress(ComplexAddress.create(r)); + cp.setGender(TestDataGenerator.getEnum(Gender.class, r)); + return cp; + } +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/Country.java b/uitest/src/com/vaadin/tests/fieldgroup/Country.java new file mode 100644 index 0000000000..4956f0a085 --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/Country.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + + +public enum Country { + FINLAND, SWEDEN, NORWAY, DENMARK, ICELAND, USA, RUSSIA, ESTONIA; + +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/Gender.java b/uitest/src/com/vaadin/tests/fieldgroup/Gender.java new file mode 100644 index 0000000000..dcc6687c17 --- /dev/null +++ b/uitest/src/com/vaadin/tests/fieldgroup/Gender.java @@ -0,0 +1,20 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.fieldgroup; + +public enum Gender { + MALE, FEMALE, UNDISCLOSED; +} diff --git a/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPosition.java b/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPosition.java new file mode 100644 index 0000000000..a8a25d03ce --- /dev/null +++ b/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPosition.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.layouts; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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. + */ +@Theme("valo") +public class HorizontalLayoutAndCaretPosition extends AbstractTestUI { + @Override + protected void setup(VaadinRequest request) { + final HorizontalLayout root = new HorizontalLayout(); + root.setSizeFull(); + addComponent(root); + root.addComponent(new TextField()); + + Label l = new Label(); + root.addComponent(l); + root.setExpandRatio(l, 1); + root.addComponent(new TextField()); + } + + @Override + protected String getTestDescription() { + return "Use IE. Enter some text to the text field. Clicking on the text should position the caret where you clicked, not cause it to jump to the start or the end"; + } + + @Override + protected Integer getTicketNumber() { + return 11152; + } + +} diff --git a/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPositionTest.java b/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPositionTest.java new file mode 100644 index 0000000000..df42e292e3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/layouts/HorizontalLayoutAndCaretPositionTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.layouts; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class HorizontalLayoutAndCaretPositionTest extends MultiBrowserTest { + + @Test + public void testCaretPositionOnClck() { + openTestURL(); + TextFieldElement first = $(TextFieldElement.class).first(); + // type in some text to the first field + first.click(); + first.sendKeys("test"); + // make sure that the field could be focused and text typed + Assert.assertEquals("Field must be focused on click", "test", + first.getValue()); + // now move the focus to the next text field + $(TextFieldElement.class).get(1).click(); + // and back to the first one + first.click(30, 10); + first.sendKeys("do_not_put_in_beginning_"); + Assert.assertNotEquals("The caret position must be maintained", + "do_not_put_in_beginning_test", first.getValue()); + } + +} diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java index 15585c7e45..c8ca4c12a1 100644 --- a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java +++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java @@ -67,6 +67,7 @@ import com.vaadin.testbench.TestBench; import com.vaadin.testbench.TestBenchDriverProxy; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.TestBenchTestCase; +import com.vaadin.testbench.elements.AbstractElement; import com.vaadin.testbench.elements.CheckBoxElement; import com.vaadin.testbench.elements.LabelElement; import com.vaadin.testbench.elements.TableElement; @@ -1332,4 +1333,16 @@ public abstract class AbstractTB3Test extends TestBenchTestCase { checkbox.findElement(By.xpath("input")).click(); } + @Override + public boolean isElementPresent(Class<? extends AbstractElement> clazz) { + // This is a bug in TB4 as isElementPresent(..) should just return true + // or false but can also throw exceptions. The problem is possibly if + // this is run when the Vaadin app is not initialized yet + try { + return super.isElementPresent(clazz); + } catch (NoSuchElementException e) { + return false; + } + } + } diff --git a/uitest/src/com/vaadin/tests/tb3/DndActionsTest.java b/uitest/src/com/vaadin/tests/tb3/DndActionsTest.java index e755a00a0d..96a2280323 100644 --- a/uitest/src/com/vaadin/tests/tb3/DndActionsTest.java +++ b/uitest/src/com/vaadin/tests/tb3/DndActionsTest.java @@ -47,4 +47,24 @@ public abstract class DndActionsTest extends MultiBrowserTest { action.build().perform(); } } + + public void dragAndDrop(WebElement element, WebElement target) { + /* + * Selenium doesn't properly drag and drop items in IE8. It tries to + * start dragging an element from a position above the element itself. + */ + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + Actions action = new Actions(getDriver()); + action.moveToElement(element); + action.moveByOffset(0, 1); + action.clickAndHold(); + action.moveToElement(target); + action.release(); + action.build().perform(); + } else { + Actions action = new Actions(getDriver()); + action.dragAndDrop(element, target); + action.build().perform(); + } + } } diff --git a/uitest/src/com/vaadin/tests/util/PersonContainer.java b/uitest/src/com/vaadin/tests/util/PersonContainer.java index a3cf28b083..611e5d3adb 100644 --- a/uitest/src/com/vaadin/tests/util/PersonContainer.java +++ b/uitest/src/com/vaadin/tests/util/PersonContainer.java @@ -24,8 +24,7 @@ public class PersonContainer extends BeanItemContainer<Person> implements "First name", "Last name", "Email", "Phone number", "Street Address", "Postal Code", "City" }; - public PersonContainer() throws InstantiationException, - IllegalAccessException { + public PersonContainer() { super(Person.class); addNestedContainerProperty("address.streetAddress"); addNestedContainerProperty("address.postalCode"); @@ -33,63 +32,22 @@ public class PersonContainer extends BeanItemContainer<Person> implements } public static PersonContainer createWithTestData() { - final String[] fnames = { "Peter", "Alice", "Joshua", "Mike", "Olivia", - "Nina", "Alex", "Rita", "Dan", "Umberto", "Henrik", "Rene", - "Lisa", "Marge" }; - final String[] lnames = { "Smith", "Gordon", "Simpson", "Brown", - "Clavel", "Simons", "Verne", "Scott", "Allison", "Gates", - "Rowling", "Barks", "Ross", "Schneider", "Tate" }; - final String cities[] = { "Amsterdam", "Berlin", "Helsinki", - "Hong Kong", "London", "Luxemburg", "New York", "Oslo", - "Paris", "Rome", "Stockholm", "Tokyo", "Turku" }; - final String streets[] = { "4215 Blandit Av.", "452-8121 Sem Ave", - "279-4475 Tellus Road", "4062 Libero. Av.", "7081 Pede. Ave", - "6800 Aliquet St.", "P.O. Box 298, 9401 Mauris St.", - "161-7279 Augue Ave", "P.O. Box 496, 1390 Sagittis. Rd.", - "448-8295 Mi Avenue", "6419 Non Av.", - "659-2538 Elementum Street", "2205 Quis St.", - "252-5213 Tincidunt St.", "P.O. Box 175, 4049 Adipiscing Rd.", - "3217 Nam Ave", "P.O. Box 859, 7661 Auctor St.", - "2873 Nonummy Av.", "7342 Mi, Avenue", - "539-3914 Dignissim. Rd.", "539-3675 Magna Avenue", - "Ap #357-5640 Pharetra Avenue", "416-2983 Posuere Rd.", - "141-1287 Adipiscing Avenue", "Ap #781-3145 Gravida St.", - "6897 Suscipit Rd.", "8336 Purus Avenue", "2603 Bibendum. Av.", - "2870 Vestibulum St.", "Ap #722 Aenean Avenue", - "446-968 Augue Ave", "1141 Ultricies Street", - "Ap #992-5769 Nunc Street", "6690 Porttitor Avenue", - "Ap #105-1700 Risus Street", - "P.O. Box 532, 3225 Lacus. Avenue", "736 Metus Street", - "414-1417 Fringilla Street", "Ap #183-928 Scelerisque Road", - "561-9262 Iaculis Avenue" }; PersonContainer c = null; Random r = new Random(0); - try { - c = new PersonContainer(); - for (int i = 0; i < 100; i++) { - Person p = new Person(); - p.setFirstName(fnames[r.nextInt(fnames.length)]); - p.setLastName(lnames[r.nextInt(lnames.length)]); - p.getAddress().setCity(cities[r.nextInt(cities.length)]); - p.setEmail(p.getFirstName().toLowerCase() + "." - + p.getLastName().toLowerCase() + "@vaadin.com"); - p.setPhoneNumber("+358 02 555 " + r.nextInt(10) + r.nextInt(10) - + r.nextInt(10) + r.nextInt(10)); - int n = r.nextInt(100000); - if (n < 10000) { - n += 10000; - } - p.getAddress().setPostalCode(n); - p.getAddress().setStreetAddress( - streets[r.nextInt(streets.length)]); - c.addItem(p); - } - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + c = new PersonContainer(); + for (int i = 0; i < 100; i++) { + Person p = new Person(); + p.setFirstName(TestDataGenerator.getFirstName(r)); + p.setLastName(TestDataGenerator.getLastName(r)); + p.getAddress().setCity(TestDataGenerator.getCity(r)); + p.setEmail(p.getFirstName().toLowerCase() + "." + + p.getLastName().toLowerCase() + "@vaadin.com"); + p.setPhoneNumber(TestDataGenerator.getPhoneNumber(r)); + + p.getAddress().setPostalCode(TestDataGenerator.getPostalCode(r)); + p.getAddress().setStreetAddress( + TestDataGenerator.getStreetAddress(r)); + c.addItem(p); } return c; diff --git a/uitest/src/com/vaadin/tests/util/TestDataGenerator.java b/uitest/src/com/vaadin/tests/util/TestDataGenerator.java new file mode 100644 index 0000000000..8becb709f0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/util/TestDataGenerator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.util; + +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; + +public class TestDataGenerator { + + final static String[] fnames = { "Peter", "Alice", "Joshua", "Mike", + "Olivia", "Nina", "Alex", "Rita", "Dan", "Umberto", "Henrik", + "Rene", "Lisa", "Marge" }; + final static String[] lnames = { "Smith", "Gordon", "Simpson", "Brown", + "Clavel", "Simons", "Verne", "Scott", "Allison", "Gates", + "Rowling", "Barks", "Ross", "Schneider", "Tate" }; + final static String cities[] = { "Amsterdam", "Berlin", "Helsinki", + "Hong Kong", "London", "Luxemburg", "New York", "Oslo", "Paris", + "Rome", "Stockholm", "Tokyo", "Turku" }; + final static String streets[] = { "4215 Blandit Av.", "452-8121 Sem Ave", + "279-4475 Tellus Road", "4062 Libero. Av.", "7081 Pede. Ave", + "6800 Aliquet St.", "P.O. Box 298, 9401 Mauris St.", + "161-7279 Augue Ave", "P.O. Box 496, 1390 Sagittis. Rd.", + "448-8295 Mi Avenue", "6419 Non Av.", "659-2538 Elementum Street", + "2205 Quis St.", "252-5213 Tincidunt St.", + "P.O. Box 175, 4049 Adipiscing Rd.", "3217 Nam Ave", + "P.O. Box 859, 7661 Auctor St.", "2873 Nonummy Av.", + "7342 Mi, Avenue", "539-3914 Dignissim. Rd.", + "539-3675 Magna Avenue", "Ap #357-5640 Pharetra Avenue", + "416-2983 Posuere Rd.", "141-1287 Adipiscing Avenue", + "Ap #781-3145 Gravida St.", "6897 Suscipit Rd.", + "8336 Purus Avenue", "2603 Bibendum. Av.", "2870 Vestibulum St.", + "Ap #722 Aenean Avenue", "446-968 Augue Ave", + "1141 Ultricies Street", "Ap #992-5769 Nunc Street", + "6690 Porttitor Avenue", "Ap #105-1700 Risus Street", + "P.O. Box 532, 3225 Lacus. Avenue", "736 Metus Street", + "414-1417 Fringilla Street", "Ap #183-928 Scelerisque Road", + "561-9262 Iaculis Avenue" }; + + public static String getStreetAddress(Random r) { + return streets[r.nextInt(streets.length)]; + } + + public static Integer getPostalCode(Random r) { + int n = r.nextInt(100000); + if (n < 10000) { + n += 10000; + } + return n; + } + + public static String getPhoneNumber(Random r) { + return "+358 02 555 " + r.nextInt(10) + r.nextInt(10) + r.nextInt(10) + + r.nextInt(10); + } + + public static String getCity(Random r) { + return cities[r.nextInt(cities.length)]; + } + + public static String getLastName(Random r) { + return lnames[r.nextInt(lnames.length)]; + } + + public static String getFirstName(Random r) { + return fnames[r.nextInt(fnames.length)]; + } + + public static int getAge(Random r) { + return r.nextInt(100) + 10; + } + + public static Date getBirthDate(Random r) { + Calendar c = Calendar.getInstance(TimeZone.getTimeZone("EET"), + new Locale("FI", "fi")); + c.setLenient(true); + c.setTimeInMillis(0); + c.set(Calendar.YEAR, r.nextInt(100) + 1900); + c.set(Calendar.MONTH, r.nextInt(12)); + c.set(Calendar.DAY_OF_MONTH, r.nextInt(31)); + + return c.getTime(); + } + + public static BigDecimal getSalary(Random r) { + return new BigDecimal(r.nextInt(80000)); + } + + public static <T extends Enum<T>> T getEnum(Class<T> class1, Random r) { + EnumSet<T> foo = EnumSet.allOf(class1); + return (T) foo.toArray()[r.nextInt(foo.size() - 1)]; + } +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientColumnRendererConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientColumnRendererConnector.java index f35f9820e0..2b8f454ed1 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientColumnRendererConnector.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientColumnRendererConnector.java @@ -114,11 +114,6 @@ public class GridClientColumnRendererConnector extends // TODO Auto-generated method stub (henrik paul: 17.6.) return null; } - - @Override - public int indexOf(String row) { - return ds.indexOf(row); - } } @Override |