diff options
author | Teemu Suo-Anttila <tsuoanttila@users.noreply.github.com> | 2017-02-22 11:35:19 +0200 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2017-02-22 11:35:19 +0200 |
commit | ca1bfa7511e35fad802271604158afd7be6531d0 (patch) | |
tree | f0c68b320cff5051a4a8dc4d5827f55276b1d73c | |
parent | e76d2e8953e8bfb80018019dc5497e7760313403 (diff) | |
download | vaadin-framework-ca1bfa7511e35fad802271604158afd7be6531d0.tar.gz vaadin-framework-ca1bfa7511e35fad802271604158afd7be6531d0.zip |
Pick changes from 7.7.7 (#8577)
* Fix java packaging order (#106)
Closes vaadin/archetypes#113
* Use proper UTF-8 encoding for Content-Disposition filenames (#19527) (#6607)
* Enable changing the backing bean for BeanItem (#4302) (#77)
When storing a bean to the database, you typically get a new and updated
bean instance back. By allowing to change the bean instance, we make it
possible to just update the single BeanItem instance which can be used
in many places.
* Make AtmospherePushConnection methods public (#7973)
There is no sensible way to use a custom version of APC, so protected
access does not help in any way to access the underlying resource and/or
connected UI.
* Use correct indexes in multiselect checkboxes after removing rows (#8072)
Fixes #8011
* Fix removal of hidden Grid columns (#8071)
Fixes #8018
* Call error handler for exceptions in UI.init() (#8055)
Fixes #4995
* Render font icon correctly on the 'more' menu item (#8126)
* Render font icon correctly on the 'more' menu item
Fixes #8125
* Reopen Grid details on attach, fixes #8015 (#8074)
Fixes #8015
* Fix broken Grid tests after picking changes from 7.7.7
Removed duplicate setDetailsVisible calls from onDetach
* Correctly detach components in merged cells when a static row is removed (#8142)
Fixes #8140
41 files changed, 1116 insertions, 95 deletions
diff --git a/client-compiler/pom.xml b/client-compiler/pom.xml index 1ccfccc666..b9aa8188ef 100644 --- a/client-compiler/pom.xml +++ b/client-compiler/pom.xml @@ -58,7 +58,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/client/pom.xml b/client/pom.xml index 53a497b183..e687aee36e 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -57,7 +57,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java index cc8e3872ad..79df33e9a7 100644 --- a/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java @@ -25,9 +25,8 @@ import com.vaadin.client.ApplicationConnection; import com.vaadin.client.Paintable; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.ImageIcon; +import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VMenuBar; import com.vaadin.shared.ui.ComponentStateUtil; @@ -77,11 +76,11 @@ public class MenuBarConnector extends AbstractComponentConnector StringBuffer itemHTML = new StringBuffer(); if (moreItemUIDL.hasAttribute("icon")) { - itemHTML.append("<img src=\"" - + WidgetUtil.escapeAttribute(client.translateVaadinUri( - moreItemUIDL.getStringAttribute("icon"))) - + "\" class=\"" + ImageIcon.CLASSNAME - + "\" alt=\"\" />"); + Icon icon = client + .getIcon(moreItemUIDL.getStringAttribute("icon")); + if (icon != null) { + itemHTML.append(icon.getElement().getString()); + } } String moreItemText = moreItemUIDL.getStringAttribute("text"); diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java index 14c2210696..8ccd4373c2 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java @@ -26,6 +26,7 @@ import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -45,6 +46,7 @@ import com.vaadin.client.widget.grid.RendererCellReference; import com.vaadin.client.widget.grid.events.GridEnabledHandler; import com.vaadin.client.widget.grid.events.GridSelectionAllowedEvent; import com.vaadin.client.widget.grid.events.GridSelectionAllowedHandler; +import com.vaadin.client.widgets.Escalator.AbstractRowContainer; import com.vaadin.client.widgets.Grid; /** @@ -330,7 +332,7 @@ public class MultiSelectionRenderer<T> int constrainedPageY = Math.max(bodyAbsoluteTop, Math.min(bodyAbsoluteBottom, pageY)); - int logicalRow = getLogicalRowIndex(WidgetUtil + int logicalRow = getLogicalRowIndex(grid, WidgetUtil .getElementFromPoint(initialPageX, constrainedPageY)); int incrementOrDecrement = (logicalRow > lastModifiedLogicalRow) ? 1 @@ -584,8 +586,6 @@ public class MultiSelectionRenderer<T> } } - private static final String LOGICAL_ROW_PROPERTY_INT = "vEscalatorLogicalRow"; - private final Grid<T> grid; private HandlerRegistration nativePreviewHandlerRegistration; @@ -629,10 +629,7 @@ public class MultiSelectionRenderer<T> public void render(final RendererCellReference cell, final Boolean data, CheckBox checkBox) { checkBox.setValue(data, false); - checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive() - && grid.getSelectionModel().isSelectionAllowed()); - checkBox.getElement().setPropertyInt(LOGICAL_ROW_PROPERTY_INT, - cell.getRowIndex()); + checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive()); } @Override @@ -666,7 +663,7 @@ public class MultiSelectionRenderer<T> private void startDragSelect(NativeEvent event, final Element target) { injectNativeHandler(); - int logicalRowIndex = getLogicalRowIndex(target); + int logicalRowIndex = getLogicalRowIndex(grid, target); autoScrollHandler.start(logicalRowIndex); event.preventDefault(); event.stopPropagation(); @@ -685,7 +682,7 @@ public class MultiSelectionRenderer<T> } } - private int getLogicalRowIndex(final Element target) { + private int getLogicalRowIndex(Grid<T> grid, final Element target) { if (target == null) { return -1; } @@ -705,7 +702,8 @@ public class MultiSelectionRenderer<T> final Element checkbox = td.getFirstChildElement(); assert checkbox != null : "Checkbox has disappeared"; - return checkbox.getPropertyInt(LOGICAL_ROW_PROPERTY_INT); + return ((AbstractRowContainer) grid.getEscalator().getBody()) + .getLogicalRowIndex((TableRowElement) tr); } tr = tr.getNextSiblingElement(); } diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java index 3c7b7c0159..239ed9b90b 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Escalator.java +++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java @@ -53,7 +53,6 @@ import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.logging.client.LogConfiguration; -import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RequiresResize; @@ -702,13 +701,13 @@ public class Escalator extends Widget /*-{ var vScroll = esc.@com.vaadin.client.widgets.Escalator::verticalScrollbar; var vScrollElem = vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); - + var hScroll = esc.@com.vaadin.client.widgets.Escalator::horizontalScrollbar; var hScrollElem = hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); - + return $entry(function(e) { var target = e.target; - + // in case the scroll event was native (i.e. scrollbars were dragged, or // the scrollTop/Left was manually modified), the bundles have old cache // values. We need to make sure that the caches are kept up to date. @@ -729,29 +728,29 @@ public class Escalator extends Widget return $entry(function(e) { var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; - + // Delta mode 0 is in pixels; we don't need to do anything... - + // A delta mode of 1 means we're scrolling by lines instead of pixels // We need to scale the number of lines by the default line height if(e.deltaMode === 1) { var brc = esc.@com.vaadin.client.widgets.Escalator::body; deltaY *= brc.@com.vaadin.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()(); } - + // Other delta modes aren't supported if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) { var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\""; - + // Print warning message esc.@com.vaadin.client.widgets.Escalator::logWarning(*)(msg); } - + // IE8 has only delta y if (isNaN(deltaY)) { deltaY = -0.5*e.wheelDelta; } - + @com.vaadin.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e); }); }-*/; @@ -1098,7 +1097,7 @@ public class Escalator extends Widget } } - protected abstract class AbstractRowContainer implements RowContainer { + public abstract class AbstractRowContainer implements RowContainer { private EscalatorUpdater updater = EscalatorUpdater.NULL; private int rows; @@ -2124,7 +2123,14 @@ public class Escalator extends Widget */ protected abstract double getHeightOfSection(); - protected int getLogicalRowIndex(final TableRowElement tr) { + /** + * Gets the logical row index for the given table row element. + * + * @param tr + * the table row element inside this container. + * @return the logical index of the given element + */ + public int getLogicalRowIndex(final TableRowElement tr) { return tr.getSectionRowIndex(); }; @@ -3433,7 +3439,7 @@ public class Escalator extends Widget } @Override - protected int getLogicalRowIndex(final TableRowElement tr) { + public int getLogicalRowIndex(final TableRowElement tr) { assert tr .getParentNode() == root : "The given element isn't a row element in the body"; int internalIndex = visualRowOrder.indexOf(tr); diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java index 9974505bd4..a135827a5a 100644..100755 --- a/client/src/main/java/com/vaadin/client/widgets/Grid.java +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -4159,6 +4159,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); /** A set keeping track of the indices of all currently open details */ private Set<Integer> visibleDetails = new HashSet<>(); + /** A set of indices of details to reopen after detach and on attach */ + private final Set<Integer> reattachVisibleDetails = new HashSet<>(); private boolean columnReorderingAllowed; @@ -6450,8 +6452,14 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, int columnIndex = columns.indexOf(column); // Remove from column configuration - escalator.getColumnConfiguration() - .removeColumns(getVisibleColumns().indexOf(column), 1); + int visibleColumnIndex = getVisibleColumns().indexOf(column); + if (visibleColumnIndex < 0) { + assert column.isHidden(); + // Hidden columns are not included in Escalator + } else { + getEscalator().getColumnConfiguration() + .removeColumns(visibleColumnIndex, 1); + } updateFrozenColumns(); @@ -6830,7 +6838,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, return editor; } - protected Escalator getEscalator() { + /** + * Gets the {@link Escalator} used by this Grid instnace. + * + * @return the escalator instance, never <code>null</code> + */ + public Escalator getEscalator() { return escalator; } @@ -8712,11 +8725,17 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, // Grid was just attached to DOM. Column widths should be calculated. recalculateColumnWidths(); + for (int row : reattachVisibleDetails) { + setDetailsVisible(row, true); + } + reattachVisibleDetails.clear(); } @Override protected void onDetach() { Set<Integer> details = new HashSet<>(visibleDetails); + reattachVisibleDetails.clear(); + reattachVisibleDetails.addAll(details); for (int row : details) { setDetailsVisible(row, false); } diff --git a/compatibility-client/pom.xml b/compatibility-client/pom.xml index 898326526c..76c06a3755 100644 --- a/compatibility-client/pom.xml +++ b/compatibility-client/pom.xml @@ -75,7 +75,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/widget/grid/selection/MultiSelectionRenderer.java b/compatibility-client/src/main/java/com/vaadin/v7/client/widget/grid/selection/MultiSelectionRenderer.java index 678d37369f..a7c974aead 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/widget/grid/selection/MultiSelectionRenderer.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/widget/grid/selection/MultiSelectionRenderer.java @@ -26,6 +26,7 @@ import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -45,6 +46,7 @@ import com.vaadin.v7.client.widget.grid.RendererCellReference; import com.vaadin.v7.client.widget.grid.events.GridEnabledEvent; import com.vaadin.v7.client.widget.grid.events.GridEnabledHandler; import com.vaadin.v7.client.widget.grid.selection.SelectionModel.Multi.Batched; +import com.vaadin.v7.client.widgets.Escalator.AbstractRowContainer; import com.vaadin.v7.client.widgets.Grid; /** @@ -319,7 +321,7 @@ public class MultiSelectionRenderer<T> int constrainedPageY = Math.max(bodyAbsoluteTop, Math.min(bodyAbsoluteBottom, pageY)); - int logicalRow = getLogicalRowIndex(WidgetUtil + int logicalRow = getLogicalRowIndex(grid, WidgetUtil .getElementFromPoint(initialPageX, constrainedPageY)); int incrementOrDecrement = (logicalRow > lastModifiedLogicalRow) ? 1 @@ -586,8 +588,6 @@ public class MultiSelectionRenderer<T> } } - private static final String LOGICAL_ROW_PROPERTY_INT = "vEscalatorLogicalRow"; - private final Grid<T> grid; private HandlerRegistration nativePreviewHandlerRegistration; @@ -633,8 +633,6 @@ public class MultiSelectionRenderer<T> CheckBox checkBox) { checkBox.setValue(data, false); checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive()); - checkBox.getElement().setPropertyInt(LOGICAL_ROW_PROPERTY_INT, - cell.getRowIndex()); } @Override @@ -668,7 +666,7 @@ public class MultiSelectionRenderer<T> private void startDragSelect(NativeEvent event, final Element target) { injectNativeHandler(); - int logicalRowIndex = getLogicalRowIndex(target); + int logicalRowIndex = getLogicalRowIndex(grid, target); autoScrollHandler.start(logicalRowIndex); event.preventDefault(); event.stopPropagation(); @@ -687,7 +685,7 @@ public class MultiSelectionRenderer<T> } } - private int getLogicalRowIndex(final Element target) { + private int getLogicalRowIndex(Grid<T> grid, final Element target) { if (target == null) { return -1; } @@ -707,7 +705,8 @@ public class MultiSelectionRenderer<T> final Element checkbox = td.getFirstChildElement(); assert checkbox != null : "Checkbox has disappeared"; - return checkbox.getPropertyInt(LOGICAL_ROW_PROPERTY_INT); + return ((AbstractRowContainer) grid.getEscalator().getBody()) + .getLogicalRowIndex((TableRowElement) tr); } tr = tr.getNextSiblingElement(); } diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java b/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java index f343d45f6f..cb2786a4cf 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java @@ -673,13 +673,13 @@ public class Escalator extends Widget /*-{ var vScroll = esc.@com.vaadin.v7.client.widgets.Escalator::verticalScrollbar; var vScrollElem = vScroll.@com.vaadin.v7.client.widget.escalator.ScrollbarBundle::getElement()(); - + var hScroll = esc.@com.vaadin.v7.client.widgets.Escalator::horizontalScrollbar; var hScrollElem = hScroll.@com.vaadin.v7.client.widget.escalator.ScrollbarBundle::getElement()(); - + return $entry(function(e) { var target = e.target; - + // in case the scroll event was native (i.e. scrollbars were dragged, or // the scrollTop/Left was manually modified), the bundles have old cache // values. We need to make sure that the caches are kept up to date. @@ -700,29 +700,29 @@ public class Escalator extends Widget return $entry(function(e) { var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; - + // Delta mode 0 is in pixels; we don't need to do anything... - + // A delta mode of 1 means we're scrolling by lines instead of pixels // We need to scale the number of lines by the default line height if(e.deltaMode === 1) { var brc = esc.@com.vaadin.v7.client.widgets.Escalator::body; deltaY *= brc.@com.vaadin.v7.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()(); } - + // Other delta modes aren't supported if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) { var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\""; - + // Print warning message esc.@com.vaadin.v7.client.widgets.Escalator::logWarning(*)(msg); } - + // IE8 has only delta y if (isNaN(deltaY)) { deltaY = -0.5*e.wheelDelta; } - + @com.vaadin.v7.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e); }); }-*/; @@ -1044,7 +1044,7 @@ public class Escalator extends Widget } } - protected abstract class AbstractRowContainer implements RowContainer { + public abstract class AbstractRowContainer implements RowContainer { private EscalatorUpdater updater = EscalatorUpdater.NULL; private int rows; @@ -2070,7 +2070,14 @@ public class Escalator extends Widget */ protected abstract double getHeightOfSection(); - protected int getLogicalRowIndex(final TableRowElement tr) { + /** + * Gets the logical row index for the given table row element. + * + * @param tr + * the table row element inside this container + * @return the logical index of the given element + */ + public int getLogicalRowIndex(final TableRowElement tr) { return tr.getSectionRowIndex(); }; @@ -3379,7 +3386,7 @@ public class Escalator extends Widget } @Override - protected int getLogicalRowIndex(final TableRowElement tr) { + public int getLogicalRowIndex(final TableRowElement tr) { assert tr .getParentNode() == root : "The given element isn't a row element in the body"; int internalIndex = visualRowOrder.indexOf(tr); diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java b/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java index e59fc2213f..5e48188128 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java @@ -4128,6 +4128,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); /** A set keeping track of the indices of all currently open details */ private Set<Integer> visibleDetails = new HashSet<>(); + /** A set of indices of details to reopen after detach and on attach */ + private final Set<Integer> reattachVisibleDetails = new HashSet<>(); private boolean columnReorderingAllowed; @@ -6451,8 +6453,14 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, int columnIndex = columns.indexOf(column); // Remove from column configuration - escalator.getColumnConfiguration() - .removeColumns(getVisibleColumns().indexOf(column), 1); + int visibleColumnIndex = getVisibleColumns().indexOf(column); + if (visibleColumnIndex < 0) { + assert column.isHidden(); + // Hidden columns are not included in Escalator + } else { + getEscalator().getColumnConfiguration() + .removeColumns(visibleColumnIndex, 1); + } updateFrozenColumns(); @@ -6831,7 +6839,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, return editor; } - protected Escalator getEscalator() { + /** + * Gets the {@link Escalator} used by this Grid instnace. + * + * @return the escalator instance, never <code>null</code> + */ + public Escalator getEscalator() { return escalator; } @@ -8766,6 +8779,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, @Override protected void onDetach() { Set<Integer> details = new HashSet<>(visibleDetails); + reattachVisibleDetails.clear(); + reattachVisibleDetails.addAll(details); for (int row : details) { setDetailsVisible(row, false); } diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/BeanItem.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/BeanItem.java index 8bba865ffc..a286cdc3b2 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/data/util/BeanItem.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/BeanItem.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import com.vaadin.data.util.BeanUtil; +import com.vaadin.v7.data.Property; /** * A wrapper class for adding the Item interface to any Java Bean. @@ -41,7 +42,7 @@ public class BeanItem<BT> extends PropertysetItem { /** * The bean which this Item is based on. */ - private final BT bean; + private BT bean; /** * <p> @@ -264,4 +265,46 @@ public class BeanItem<BT> extends PropertysetItem { return bean; } + /** + * Changes the Java Bean this item is based on. + * <p> + * This will cause any existing properties to be re-mapped to the new bean. + * Any added custom properties which are not of type {@link MethodProperty} + * or {@link NestedMethodProperty} will not be updated to reflect the change + * of bean. + * <p> + * Changing the bean will fire value change events for all properties of + * type {@link MethodProperty} or {@link NestedMethodProperty}. + * + * @param bean + * The new bean to use for this item, not <code>null</code> + */ + public void setBean(BT bean) { + if (bean == null) { + throw new IllegalArgumentException("Bean cannot be null"); + } + + if (getBean().getClass() != bean.getClass()) { + throw new IllegalArgumentException( + "The new bean class " + bean.getClass().getName() + + " does not match the old bean class " + + getBean().getClass()); + } + + // Remap properties + for (Object propertyId : getItemPropertyIds()) { + Property p = getItemProperty(propertyId); + if (p instanceof MethodProperty) { + MethodProperty mp = (MethodProperty) p; + assert (mp.getInstance() == getBean()); + mp.setInstance(bean); + } else if (p instanceof NestedMethodProperty) { + NestedMethodProperty nmp = (NestedMethodProperty) p; + assert (nmp.getInstance() == getBean()); + nmp.setInstance(bean); + } + } + + this.bean = bean; + } } diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java index ba5edbb146..e3651d0272 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java @@ -769,6 +769,37 @@ public class MethodProperty<T> extends AbstractProperty<T> { super.fireValueChange(); } + /** + * The instance used by this property + * + * @return the instance used for fetching the property value + */ + public Object getInstance() { + return instance; + } + + /** + * Sets the instance used by this property. + * <p> + * The new instance must be of the same type as the old instance + * <p> + * To be consistent with {@link #setValue(Object)}, this method will fire a + * value change event even if the value stays the same + * + * @param instance + * the instance to use + */ + public void setInstance(Object instance) { + if (this.instance.getClass() != instance.getClass()) { + throw new IllegalArgumentException("The new instance is of type " + + instance.getClass().getName() + + " which does not match the old instance type " + + this.instance.getClass().getName()); + } + this.instance = instance; + fireValueChange(); + } + private static final Logger getLogger() { return Logger.getLogger(MethodProperty.class.getName()); } diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java index 3f9236c4ac..ab23d07551 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java @@ -193,9 +193,10 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { /** * Gets the value stored in the Property. The value is resolved by calling - * the specified getter method with the argument specified at instantiation. + * the specified getter methods on the current instance: * * @return the value of the Property + * @see #getInstance() */ @Override public T getValue() { @@ -267,4 +268,35 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { return Collections.unmodifiableList(getMethods); } + /** + * The instance used by this property + * + * @return the instance used for fetching the property value + */ + public Object getInstance() { + return instance; + } + + /** + * Sets the instance used by this property. + * <p> + * The new instance must be of the same type as the old instance + * <p> + * To be consistent with {@link #setValue(Object)}, this method will fire a + * value change event even if the value stays the same + * + * @param instance + * the instance to use + */ + public void setInstance(Object instance) { + if (this.instance.getClass() != instance.getClass()) { + throw new IllegalArgumentException("The new instance is of type " + + instance.getClass().getName() + + " which does not match the old instance type " + + this.instance.getClass().getName()); + } + this.instance = instance; + fireValueChange(); + } + } diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java index cabba91d31..ee9b968c00 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java @@ -2556,6 +2556,9 @@ public class Grid extends AbstractComponent for (CELLTYPE cell : cells.values()) { cell.detach(); } + for (CELLTYPE cell : cellGroups.values()) { + cell.detach(); + } } } diff --git a/compatibility-server/src/test/java/com/vaadin/v7/data/util/BeanItemTest.java b/compatibility-server/src/test/java/com/vaadin/v7/data/util/BeanItemTest.java index cc5d72f3df..a9eb3d740b 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/data/util/BeanItemTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/data/util/BeanItemTest.java @@ -386,4 +386,41 @@ public class BeanItemTest { // Should not be exception property.setValue(null); } + + @Test + public void testChangeBean() { + BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>( + new MyClass("Foo")); + beanItem.setBean(new MyClass("Bar")); + Assert.assertEquals("Bar", beanItem.getItemProperty("name").getValue()); + } + + @Test + public void testChangeBeanNestedProperty() { + BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>( + new MyClass("Foo")); + beanItem.setBean(new MyClass("Bar")); + Assert.assertEquals("Bar", beanItem.getItemProperty("name").getValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void testChangeBeanToIncompatibleOne() { + BeanItem<Object> beanItem = new BeanItem<Object>(new MyClass("Foo")); + beanItem.setBean(new Generic<String>()); + } + + @Test(expected = IllegalArgumentException.class) + public void testChangeBeanToSubclass() { + BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>( + new MyClass("Foo")); + beanItem.setBean(new MyClass("Bar")); + beanItem.setBean(new MyClass2("foo")); + } + + @Test(expected = IllegalArgumentException.class) + public void testChangeBeanToNull() { + BeanItem<Object> beanItem = new BeanItem<Object>(new MyClass("Foo")); + beanItem.setBean(null); + } + } diff --git a/compatibility-server/src/test/java/com/vaadin/v7/data/util/MethodPropertyTest.java b/compatibility-server/src/test/java/com/vaadin/v7/data/util/MethodPropertyTest.java new file mode 100644 index 0000000000..61c66b9167 --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/v7/data/util/MethodPropertyTest.java @@ -0,0 +1,75 @@ +/* + * 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.v7.data.util; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.v7.data.util.NestedMethodPropertyTest.Address; + +public class MethodPropertyTest { + + private Address testObject; + + @Before + public void setup() { + testObject = new Address("some street", 123); + } + + @Test + public void getValue() { + MethodProperty<String> mp = new MethodProperty<String>(testObject, + "street"); + Assert.assertEquals("some street", mp.getValue()); + } + + @Test + public void getValueAfterBeanUpdate() { + MethodProperty<String> mp = new MethodProperty<String>(testObject, + "street"); + testObject.setStreet("Foo street"); + Assert.assertEquals("Foo street", mp.getValue()); + } + + @Test + public void setValue() { + MethodProperty<String> mp = new MethodProperty<String>(testObject, + "street"); + mp.setValue("Foo street"); + Assert.assertEquals("Foo street", testObject.getStreet()); + } + + @Test + public void changeInstance() { + MethodProperty<String> mp = new MethodProperty<String>(testObject, + "street"); + Address newStreet = new Address("new street", 999); + mp.setInstance(newStreet); + Assert.assertEquals("new street", mp.getValue()); + Assert.assertEquals("some street", testObject.getStreet()); + + } + + @Test(expected = IllegalArgumentException.class) + public void changeInstanceToIncompatible() { + MethodProperty<String> mp = new MethodProperty<String>(testObject, + "street"); + mp.setInstance("foobar"); + + } + +} diff --git a/compatibility-server/src/test/java/com/vaadin/v7/data/util/NestedMethodPropertyTest.java b/compatibility-server/src/test/java/com/vaadin/v7/data/util/NestedMethodPropertyTest.java index a5103e0e0a..9a47e395e2 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/data/util/NestedMethodPropertyTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/data/util/NestedMethodPropertyTest.java @@ -348,4 +348,27 @@ public class NestedMethodPropertyTest { Assert.assertTrue(booleanProperty.isReadOnly()); } + @Test + public void testChangeInstance() { + NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>( + vaadin, "manager.address.street"); + + Address somewhere = new Address("The street", 1234); + Person someone = new Person("Someone", somewhere); + Team someteam = new Team("The team", someone); + streetProperty.setInstance(someteam); + + Assert.assertEquals("The street", streetProperty.getValue()); + Assert.assertEquals("Ruukinkatu 2-4", vaadin.getManager().getAddress() + .getStreet()); + } + + @Test(expected = IllegalArgumentException.class) + public void testChangeInstanceToIncompatible() { + NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>( + vaadin, "manager.address.street"); + + streetProperty.setInstance("bar"); + } + } diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/grid/GridChildrenTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/grid/GridChildrenTest.java index 7a599f2745..2d8edddac1 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/grid/GridChildrenTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/grid/GridChildrenTest.java @@ -18,6 +18,7 @@ package com.vaadin.v7.tests.server.component.grid; import java.util.Iterator; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import com.vaadin.ui.Component; @@ -28,12 +29,19 @@ import com.vaadin.v7.ui.Grid.HeaderCell; public class GridChildrenTest { - @Test - public void componentsInMergedHeader() { - Grid grid = new Grid(); + private Grid grid; + + @Before + public void createGrid() { + grid = new Grid(); grid.addColumn("foo"); grid.addColumn("bar"); grid.addColumn("baz"); + + } + + @Test + public void iteratorFindsComponentsInMergedHeader() { HeaderCell merged = grid.getDefaultHeaderRow().join("foo", "bar", "baz"); Label label = new Label(); @@ -44,16 +52,55 @@ public class GridChildrenTest { } @Test + public void removeComponentInMergedHeaderCell() { + HeaderCell merged = grid.getDefaultHeaderRow().join("foo", "bar", + "baz"); + Label label = new Label(); + merged.setComponent(label); + Assert.assertEquals(grid, label.getParent()); + merged.setText("foo"); + Assert.assertNull(label.getParent()); + } + + @Test + public void removeHeaderWithComponentInMergedHeaderCell() { + HeaderCell merged = grid.getDefaultHeaderRow().join("foo", "bar", + "baz"); + Label label = new Label(); + merged.setComponent(label); + Assert.assertEquals(grid, label.getParent()); + grid.removeHeaderRow(0); + Assert.assertNull(label.getParent()); + } + + @Test + public void removeComponentInMergedFooterCell() { + FooterCell merged = grid.addFooterRowAt(0).join("foo", "bar", "baz"); + Label label = new Label(); + merged.setComponent(label); + Assert.assertEquals(grid, label.getParent()); + merged.setText("foo"); + Assert.assertNull(label.getParent()); + } + + @Test + public void removeFooterWithComponentInMergedFooterCell() { + FooterCell merged = grid.addFooterRowAt(0).join("foo", "bar", "baz"); + Label label = new Label(); + merged.setComponent(label); + Assert.assertEquals(grid, label.getParent()); + grid.removeFooterRow(0); + Assert.assertNull(label.getParent()); + } + + @Test public void componentsInMergedFooter() { - Grid grid = new Grid(); - grid.addColumn("foo"); - grid.addColumn("bar"); - grid.addColumn("baz"); FooterCell merged = grid.addFooterRowAt(0).join("foo", "bar", "baz"); Label label = new Label(); merged.setComponent(label); Iterator<Component> i = grid.iterator(); Assert.assertEquals(label, i.next()); Assert.assertFalse(i.hasNext()); + Assert.assertEquals(grid, label.getParent()); } } diff --git a/compatibility-shared/pom.xml b/compatibility-shared/pom.xml index d702702748..abdc1ca849 100644 --- a/compatibility-shared/pom.xml +++ b/compatibility-shared/pom.xml @@ -75,7 +75,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/server/pom.xml b/server/pom.xml index 479935666c..4a31cd47eb 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -165,7 +165,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/server/src/main/java/com/vaadin/server/DownloadStream.java b/server/src/main/java/com/vaadin/server/DownloadStream.java index a0a1334135..b52b2e83a4 100644 --- a/server/src/main/java/com/vaadin/server/DownloadStream.java +++ b/server/src/main/java/com/vaadin/server/DownloadStream.java @@ -20,14 +20,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.http.HttpServletResponse; +import com.vaadin.util.EncodeUtil; + /** * Downloadable stream. * <p> @@ -329,13 +329,10 @@ public class DownloadStream implements Serializable { * @return A value for inclusion in a Content-Disposition header */ public static String getContentDispositionFilename(String filename) { - try { - String encodedFilename = URLEncoder.encode(filename, "UTF-8"); - return String.format("filename=\"%s\"; filename*=utf-8''%s", - encodedFilename, encodedFilename); - } catch (UnsupportedEncodingException e) { - return null; - } + String encodedFilename = EncodeUtil.rfc5987Encode(filename); + + return String.format("filename=\"%s\"; filename*=utf-8''%s", + encodedFilename, encodedFilename); } /** diff --git a/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java index 92d675001b..b5a6f55e4c 100644 --- a/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java +++ b/server/src/main/java/com/vaadin/server/communication/AtmospherePushConnection.java @@ -258,17 +258,21 @@ public class AtmospherePushConnection implements PushConnection { } /** - * @return the UI associated with this connection. + * Gets the UI this push connection is associated with. + * + * @return the UI associated with this connection */ - protected UI getUI() { + public UI getUI() { return ui; } /** - * @return The AtmosphereResource associated with this connection or null if - * connection not open. + * Gets the atmosphere resource associated with this connection. + * + * @return The AtmosphereResource associated with this connection or + * <code>null</code> if the connection is not open. */ - protected AtmosphereResource getResource() { + public AtmosphereResource getResource() { return resource; } diff --git a/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java b/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java index 09d2b0aa79..c0c6850d32 100644 --- a/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/main/java/com/vaadin/server/communication/UIInitHandler.java @@ -211,10 +211,17 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { // Set thread local here so it is available in init UI.setCurrent(ui); - ui.doInit(request, uiId.intValue(), embedId); - + Exception initException = null; + try { + ui.doInit(request, uiId.intValue(), embedId); + } catch (Exception e) { + initException = e; + } session.addUI(ui); - + if (initException != null) { + ui.getSession().getCommunicationManager() + .handleConnectorRelatedException(ui, initException); + } // Warn if the window can't be preserved if (embedId == null && vaadinService.preserveUIOnRefresh(provider, event)) { diff --git a/server/src/main/java/com/vaadin/util/EncodeUtil.java b/server/src/main/java/com/vaadin/util/EncodeUtil.java new file mode 100644 index 0000000000..eae0890542 --- /dev/null +++ b/server/src/main/java/com/vaadin/util/EncodeUtil.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2016 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.util; + +import java.nio.charset.Charset; + +/** + * Utilities related to various encoding schemes. + * + * @author Vaadin Ltd + * @since + */ +public final class EncodeUtil { + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private EncodeUtil() { + // Static utils only + } + + /** + * Encodes the given string to UTF-8 <code>value-chars</code> as defined in + * RFC5987 for use in e.g. the <code>Content-Disposition</code> HTTP header. + * + * @param value + * the string to encode, not <code>null</code> + * @return the encoded string + */ + public static String rfc5987Encode(String value) { + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < value.length();) { + int cp = value.codePointAt(i); + if (cp < 127 && (Character.isLetterOrDigit(cp) || cp == '.')) { + builder.append((char) cp); + } else { + // Create string from a single code point + String cpAsString = new String(new int[] { cp }, 0, 1); + + appendHexBytes(builder, cpAsString.getBytes(UTF8)); + } + + // Advance to the next code point + i += Character.charCount(cp); + } + + return builder.toString(); + } + + private static void appendHexBytes(StringBuilder builder, byte[] bytes) { + for (byte byteValue : bytes) { + // mask with 0xFF to compensate for "negative" values + int intValue = byteValue & 0xFF; + String hexCode = Integer.toString(intValue, 16); + builder.append('%').append(hexCode); + } + } + +} diff --git a/server/src/test/java/com/vaadin/server/DownloadStreamTest.java b/server/src/test/java/com/vaadin/server/DownloadStreamTest.java index 81f45f6173..4bb97de79f 100644 --- a/server/src/test/java/com/vaadin/server/DownloadStreamTest.java +++ b/server/src/test/java/com/vaadin/server/DownloadStreamTest.java @@ -7,13 +7,16 @@ import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.InputStream; -import java.net.URLEncoder; import org.junit.Before; import org.junit.Test; public class DownloadStreamTest { - private final String filename = "日本語.png"; + private String filename = "A å日.png"; + private String encodedFileName = "A" + "%20" // space + + "%c3%a5" // å + + "%e6%97%a5" // 日 + + ".png"; private DownloadStream stream; @Before @@ -27,7 +30,6 @@ public class DownloadStreamTest { stream.writeResponse(mock(VaadinRequest.class), response); - String encodedFileName = URLEncoder.encode(filename, "utf-8"); verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION), contains(String.format("filename=\"%s\";", encodedFileName))); verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION), diff --git a/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java b/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java new file mode 100644 index 0000000000..6c61922307 --- /dev/null +++ b/server/src/test/java/com/vaadin/server/communication/ServletUIInitHandlerTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2000-2016 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.server.communication; + +import java.io.IOException; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.server.DefaultDeploymentConfiguration; +import com.vaadin.server.ErrorEvent; +import com.vaadin.server.ErrorHandler; +import com.vaadin.server.LegacyCommunicationManager; +import com.vaadin.server.MockServletConfig; +import com.vaadin.server.UIClassSelectionEvent; +import com.vaadin.server.UICreateEvent; +import com.vaadin.server.UIProvider; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletRequest; +import com.vaadin.server.VaadinServletResponse; +import com.vaadin.server.VaadinServletService; +import com.vaadin.tests.util.AlwaysLockedVaadinSession; +import com.vaadin.ui.UI; + +public class ServletUIInitHandlerTest { + + public static class CommunicationMock { + + public final UI ui; + public final ServletConfig servletConfig; + public final VaadinServlet servlet; + public final DefaultDeploymentConfiguration deploymentConfiguration; + public final VaadinServletService service; + public final AlwaysLockedVaadinSession session; + + public CommunicationMock(final UI ui) throws Exception { + servletConfig = new MockServletConfig(); + servlet = new VaadinServlet(); + servlet.init(servletConfig); + + deploymentConfiguration = new DefaultDeploymentConfiguration( + UI.class, new Properties()); + + service = new VaadinServletService(servlet, + deploymentConfiguration); + session = new AlwaysLockedVaadinSession(service); + LegacyCommunicationManager communicationManager = new LegacyCommunicationManager( + session); + session.setCommunicationManager(communicationManager); + session.setConfiguration(deploymentConfiguration); + session.addUIProvider(new UIProvider() { + + @Override + public Class<? extends UI> getUIClass( + UIClassSelectionEvent event) { + return ui.getClass(); + } + + @Override + public UI createInstance(UICreateEvent event) { + return ui; + } + }); + this.ui = ui; + } + + public VaadinRequest createInitRequest() { + return new VaadinServletRequest( + Mockito.mock(HttpServletRequest.class), service) { + @Override + public String getMethod() { + return "POST"; + } + + @Override + public String getParameter(String name) { + if (UIInitHandler.BROWSER_DETAILS_PARAMETER.equals(name)) { + return "1"; + } + return super.getParameter(name); + } + + }; + } + + } + + @Test + public void errorHandlerForInitException() throws Exception { + final AtomicInteger pre = new AtomicInteger(0); + final AtomicInteger errorHandlerCalls = new AtomicInteger(0); + + UI ui = new UI() { + @Override + protected void init(VaadinRequest request) { + pre.incrementAndGet(); + throw new RuntimeException("Exception produced in init()"); + } + }; + + ui.setErrorHandler(new ErrorHandler() { + @Override + public void error(ErrorEvent event) { + errorHandlerCalls.incrementAndGet(); + } + }); + + CommunicationMock mock = new CommunicationMock(ui); + VaadinRequest initRequest = mock.createInitRequest(); + + ServletUIInitHandler servletUIInitHandler = new ServletUIInitHandler(); + servletUIInitHandler.handleRequest(mock.session, initRequest, + new VaadinServletResponse( + Mockito.mock(HttpServletResponse.class), mock.service) { + @Override + public ServletOutputStream getOutputStream() + throws IOException { + return new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + } + }; + } + }); + + Assert.assertEquals(1, pre.getAndIncrement()); + Assert.assertEquals(1, errorHandlerCalls.getAndIncrement()); + Assert.assertEquals(mock.session, ui.getSession()); + } + + @Test + public void initExceptionNoErrorHandler() throws Exception { + final AtomicInteger pre = new AtomicInteger(0); + + UI ui = new UI() { + @Override + protected void init(VaadinRequest request) { + pre.incrementAndGet(); + throw new RuntimeException("Exception produced in init()"); + } + }; + + CommunicationMock mock = new CommunicationMock(ui); + VaadinRequest initRequest = mock.createInitRequest(); + + ServletUIInitHandler servletUIInitHandler = new ServletUIInitHandler(); + servletUIInitHandler.handleRequest(mock.session, initRequest, + new VaadinServletResponse( + Mockito.mock(HttpServletResponse.class), mock.service) { + @Override + public ServletOutputStream getOutputStream() + throws IOException { + return new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + } + }; + } + }); + + Assert.assertEquals(1, pre.getAndIncrement()); + // Default error handler only logs the exception + Assert.assertEquals(mock.session, ui.getSession()); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java index fca87ccbdf..a3375585c7 100644 --- a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java +++ b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java @@ -74,6 +74,7 @@ public class ClassesSerializableTest { // interfaces "com\\.vaadin\\.server\\.LegacyCommunicationManager.*", // "com\\.vaadin\\.buildhelpers.*", // + "com\\.vaadin\\.util\\.EncodeUtil.*", // "com\\.vaadin\\.util\\.ReflectTools.*", // "com\\.vaadin\\.data\\.util\\.ReflectTools.*", // "com\\.vaadin\\.data\\.util\\.JsonUtil.*", // @@ -235,6 +236,7 @@ public class ClassesSerializableTest { Assert.fail( "Serializable not implemented by the following classes and interfaces: " + nonSerializableString); + } private static boolean isFunctionalType(Type type) { diff --git a/server/src/test/java/com/vaadin/util/EncodeUtilTest.java b/server/src/test/java/com/vaadin/util/EncodeUtilTest.java new file mode 100644 index 0000000000..7bac539968 --- /dev/null +++ b/server/src/test/java/com/vaadin/util/EncodeUtilTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2016 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.util; + +import org.junit.Assert; +import org.junit.Test; + +public class EncodeUtilTest { + @Test + public void rfc5987Encode() { + Assert.assertEquals("A", EncodeUtil.rfc5987Encode("A")); + Assert.assertEquals("%20", EncodeUtil.rfc5987Encode(" ")); + Assert.assertEquals("%c3%a5", EncodeUtil.rfc5987Encode("å")); + Assert.assertEquals("%e6%97%a5", EncodeUtil.rfc5987Encode("日")); + + Assert.assertEquals("A" + "%20" + "%c3%a5" + "%e6%97%a5", + EncodeUtil.rfc5987Encode("A å日")); + } +} diff --git a/shared/pom.xml b/shared/pom.xml index c896f6f744..6956e6c490 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -79,7 +79,7 @@ <execution> <id>copy-sources</id> <!-- here the phase you need --> - <phase>prepare-package</phase> + <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> diff --git a/uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java b/uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java index 141ef664b4..e74a7128d5 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java +++ b/uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java @@ -99,8 +99,8 @@ public class FileDownloaderUI extends AbstractTestUIWithLog { addComponents("Class resource pdf", resource, components); Button downloadUtf8File = new Button("Download UTF-8 named file"); - FileDownloader fd = new FileDownloader( - new ClassResource(new EmbeddedPdf().getClass(), "åäö-日本語.pdf")); + FileDownloader fd = new FileDownloader(new ClassResource( + new EmbeddedPdf().getClass(), "File åäö-日本語.pdf")); fd.setOverrideContentType(false); fd.extend(downloadUtf8File); addComponent(downloadUtf8File); diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridDetailsReattach.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDetailsReattach.java new file mode 100755 index 0000000000..0328d87d07 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDetailsReattach.java @@ -0,0 +1,37 @@ +package com.vaadin.tests.components.grid;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.VerticalLayout;
+
+public class GridDetailsReattach extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ final VerticalLayout verticalMain = new VerticalLayout();
+
+ final VerticalLayout layoutWithGrid = new VerticalLayout();
+
+ Grid<String> grid = new Grid<>("Grid");
+ grid.addColumn(String::toString).setCaption("Foo");
+ grid.setHeight("150px");
+ grid.setItems("Foo");
+ grid.setDetailsGenerator(str -> new Label("AnyDetails"));
+ grid.setDetailsVisible("Foo", true);
+ layoutWithGrid.addComponent(grid);
+
+ Button addCaptionToLayoutWithGridButton = new Button(
+ "Add caption to 'layoutWithGrid' layout");
+ addCaptionToLayoutWithGridButton.addClickListener(e -> layoutWithGrid
+ .setCaption("Caption added to 'layoutWithGrid' layout"));
+ layoutWithGrid.addComponent(addCaptionToLayoutWithGridButton);
+
+ verticalMain.addComponent(layoutWithGrid);
+
+ addComponent(verticalMain);
+
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/menubar/MenuBarIcons.java b/uitest/src/main/java/com/vaadin/tests/components/menubar/MenuBarIcons.java new file mode 100644 index 0000000000..9352daed83 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/menubar/MenuBarIcons.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2016 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.menubar; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; +import com.vaadin.server.ThemeResource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.MenuBar; +import com.vaadin.ui.MenuBar.MenuItem; + +@Theme("tests-valo") +public class MenuBarIcons extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + MenuBar fontIconMenu = new MenuBar(); + fontIconMenu.setId("fontIcon"); + fontIconMenu.setWidth("400px"); + fontIconMenu.addItem("Main", FontAwesome.MAIL_REPLY_ALL, null); + MenuItem hasSub = fontIconMenu.addItem("Has sub", FontAwesome.SUBWAY, + null); + hasSub.addItem("Sub item", FontAwesome.AMBULANCE, null); + for (int i = 0; i < 10; i++) { + fontIconMenu.addItem("Filler " + i, FontAwesome.ANGELLIST, null); + } + MenuItem more = fontIconMenu.getMoreMenuItem(); + more.setText("More"); + more.setIcon(FontAwesome.MOTORCYCLE); + addComponent(fontIconMenu); + + MenuBar menu = new MenuBar(); + menu.setId("image"); + menu.setWidth("400px"); + Resource imageIcon = new ThemeResource("img/email-reply.png"); + menu.addItem("Main", imageIcon, null); + hasSub = menu.addItem("Has sub", imageIcon, null); + hasSub.addItem("Sub item", imageIcon, null); + for (int i = 0; i < 10; i++) { + menu.addItem("Filler " + i, imageIcon, null); + } + more = menu.getMoreMenuItem(); + more.setText("More"); + more.setIcon(imageIcon); + addComponent(menu); + + } + +} diff --git a/uitest/src/main/java/com/vaadin/tests/components/ui/UIInitException.java b/uitest/src/main/java/com/vaadin/tests/components/ui/UIInitException.java index 22b6a8d031..76573d1f81 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/ui/UIInitException.java +++ b/uitest/src/main/java/com/vaadin/tests/components/ui/UIInitException.java @@ -1,12 +1,22 @@ package com.vaadin.tests.components.ui; +import com.vaadin.server.ErrorHandler; import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractReindeerTestUI; +import com.vaadin.ui.Label; public class UIInitException extends AbstractReindeerTestUI { @Override protected void setup(VaadinRequest request) { + setErrorHandler(new ErrorHandler() { + @Override + public void error(com.vaadin.server.ErrorEvent event) { + addComponent(new Label("An exception occurred: " + + event.getThrowable().getMessage())); + + } + }); throw new RuntimeException("Catch me if you can"); } diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumn.java b/uitest/src/main/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumn.java new file mode 100644 index 0000000000..1c053ca8d7 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumn.java @@ -0,0 +1,60 @@ +package com.vaadin.v7.tests.components.grid; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.tests.data.bean.Person; +import com.vaadin.tests.data.bean.Sex; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.v7.data.util.BeanItemContainer; +import com.vaadin.v7.ui.Grid; +import com.vaadin.v7.ui.Grid.Column; + +public class RemoveHiddenColumn extends AbstractTestUIWithLog { + + private final Grid testGrid = new Grid(); + private final Button testBtn = new Button("updateGrid"); + private final Button testBtn2 = new Button("show/hide Grid"); + private final HorizontalLayout buttonBar = new HorizontalLayout(testBtn, + testBtn2); + private final VerticalLayout mainLayout = new VerticalLayout(testGrid, + buttonBar); + + @Override + protected void setup(final VaadinRequest request) { + final Grid grid = new Grid(); + final BeanItemContainer<Person> bic = new BeanItemContainer<Person>( + Person.class); + grid.setContainerDataSource(bic); + grid.setColumns("firstName", "lastName", "email", "age"); + + grid.getColumn("firstName").setHidden(true); + grid.getColumn("email").setHidden(true); + + Button addRow = new Button("Add data row", new ClickListener() { + @Override + public void buttonClick(ClickEvent e) { + bic.addBean(new Person("first", "last", "email", 42, Sex.FEMALE, + null)); + + } + }); + addRow.setId("add"); + Button removeColumn = new Button("Remove first column", + new ClickListener() { + @Override + public void buttonClick(ClickEvent e) { + Column column = grid.getColumns().get(0); + log("Removed column '" + column.getHeaderCaption() + "'" + + (column.isHidden() ? " (hidden)" : "")); + grid.removeColumn(column.getPropertyId()); + } + }); + removeColumn.setId("remove"); + + addComponents(grid, addRow, removeColumn); + } +} diff --git a/uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf b/uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf Binary files differindex e44a87e9ad..e44a87e9ad 100644 --- a/uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf +++ b/uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf diff --git a/uitest/src/main/themes/VAADIN/themes/tests-valo/img/email-reply.png b/uitest/src/main/themes/VAADIN/themes/tests-valo/img/email-reply.png Binary files differnew file mode 100644 index 0000000000..be7fd2244d --- /dev/null +++ b/uitest/src/main/themes/VAADIN/themes/tests-valo/img/email-reply.png diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridDetailsReattachTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridDetailsReattachTest.java new file mode 100755 index 0000000000..a6fa1dae94 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridDetailsReattachTest.java @@ -0,0 +1,34 @@ +package com.vaadin.tests.components.grid;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.By;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.parallel.TestCategory;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+@TestCategory("grid")
+public class GridDetailsReattachTest extends MultiBrowserTest {
+
+ @Before
+ public void setUp() {
+ setDebug(true);
+ }
+
+ @Test
+ public void clickToAddCaption() {
+ openTestURL();
+ Assert.assertTrue("Grid details don't exist", hasDetailsElement());
+ $(ButtonElement.class).first().click();
+ Assert.assertTrue("Grid details don't exist after deattach and reattach",hasDetailsElement() );
+ }
+
+ private final By locator = By.className("v-grid-spacer");
+
+ private boolean hasDetailsElement() {
+ return !findElements(locator).isEmpty();
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java index 912a1eb996..25a81984ea 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java @@ -3,6 +3,11 @@ package com.vaadin.tests.components.grid; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; @@ -403,6 +408,53 @@ public class GridSelectionTest extends GridBasicsTest { assertTrue(getGridElement().getRow(0).isSelected()); } + @Test + @Ignore("Removing rows is not implemented in the UI") + public void testRemoveSelectedRowMulti() { + openTestURL(); + + setSelectionModelMulti(); + GridElement grid = getGridElement(); + grid.getCell(5, 0).click(); + + selectMenuPath("Component", "Body rows", "Remove selected rows"); + assertSelected(); + grid.getCell(5, 0).click(); + assertSelected(5); + grid.getCell(6, 0).click(); + assertSelected(5, 6); + grid.getCell(5, 0).click(); + assertSelected(6); + grid.getCell(5, 0).click(); + grid.getCell(4, 0).click(); + selectMenuPath("Component", "Body rows", "Remove selected rows"); + assertSelected(); + grid.getCell(0, 0).click(); + assertSelected(0); + grid.getCell(5, 0).click(); + assertSelected(0, 5); + grid.getCell(6, 0).click(); + assertSelected(0, 5, 6); + + } + + private void assertSelected(Integer... selected) { + GridElement grid = getGridElement(); + HashSet<Integer> expected = new HashSet<Integer>( + Arrays.asList(selected)); + for (int i = 0; i < 10; i++) { + boolean rowSelected = grid.getRow(i).isSelected(); + if (expected.contains(i)) { + Assert.assertTrue("Expected row " + i + " to be selected", + rowSelected); + } else { + Assert.assertFalse("Expected row " + i + " not to be selected", + rowSelected); + } + } + + } + private void toggleUserSelectionAllowed() { selectMenuPath("Component", "State", "Disallow user selection"); } diff --git a/uitest/src/test/java/com/vaadin/tests/components/menubar/MenuBarIconsTest.java b/uitest/src/test/java/com/vaadin/tests/components/menubar/MenuBarIconsTest.java new file mode 100644 index 0000000000..7213d2d466 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/menubar/MenuBarIconsTest.java @@ -0,0 +1,82 @@ +package com.vaadin.tests.components.menubar; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.WebElement; + +import com.vaadin.server.FontAwesome; +import com.vaadin.testbench.By; +import com.vaadin.testbench.elements.MenuBarElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class MenuBarIconsTest extends SingleBrowserTest { + + @Test + public void fontIconsRendered() { + openTestURL(); + MenuBarElement menu = $(MenuBarElement.class).id("fontIcon"); + WebElement moreItem = menu + .findElements(By.className("v-menubar-menuitem")).get(3); + + assertFontIcon(FontAwesome.MAIL_REPLY_ALL, + menu.findElement(By.vaadin("#Main"))); + WebElement hasSubElement = menu.findElement(By.vaadin("#Has sub")); + assertFontIcon(FontAwesome.SUBWAY, hasSubElement); + assertFontIcon(FontAwesome.ANGELLIST, + menu.findElement(By.vaadin("#Filler 0"))); + + hasSubElement.click(); + + assertFontIcon(FontAwesome.AMBULANCE, + hasSubElement.findElement(By.vaadin("#Sub item"))); + // Close sub menu + hasSubElement.click(); + + assertFontIcon(FontAwesome.MOTORCYCLE, moreItem); + + moreItem.click(); + WebElement filler5 = moreItem.findElement(By.vaadin("#Filler 5")); + assertFontIcon(FontAwesome.ANGELLIST, filler5); + + } + + @Test + public void imageIconsRendered() { + openTestURL(); + MenuBarElement menu = $(MenuBarElement.class).id("image"); + WebElement moreItem = menu + .findElements(By.className("v-menubar-menuitem")).get(3); + + String image = "/tests-valo/img/email-reply.png"; + assertImage(image, menu.findElement(By.vaadin("#Main"))); + WebElement hasSubElement = menu.findElement(By.vaadin("#Has sub")); + assertImage(image, hasSubElement); + assertImage(image, menu.findElement(By.vaadin("#Filler 0"))); + + hasSubElement.click(); + + assertImage(image, hasSubElement.findElement(By.vaadin("#Sub item"))); + // Close sub menu + hasSubElement.click(); + + assertImage(image, moreItem); + + moreItem.click(); + WebElement filler5 = moreItem.findElement(By.vaadin("#Filler 5")); + assertImage(image, filler5); + + } + + private void assertImage(String image, WebElement menuItem) { + WebElement imageElement = menuItem.findElement(By.className("v-icon")); + Assert.assertTrue(imageElement.getAttribute("src").endsWith(image)); + } + + private void assertFontIcon(FontAwesome expected, WebElement menuItem) { + WebElement mainIcon = menuItem.findElement(By.className("v-icon")); + + Assert.assertEquals(expected.getCodepoint(), + mainIcon.getText().codePointAt(0)); + + } +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/ui/UIInitExceptionTest.java b/uitest/src/test/java/com/vaadin/tests/components/ui/UIInitExceptionTest.java index c1d6aaa55b..064132d4c7 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/ui/UIInitExceptionTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/ui/UIInitExceptionTest.java @@ -3,9 +3,9 @@ package com.vaadin.tests.components.ui; import org.junit.Assert; import org.junit.Test; -import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.tests.tb3.SingleBrowserTest; -public class UIInitExceptionTest extends MultiBrowserTest { +public class UIInitExceptionTest extends SingleBrowserTest { @Test public void testExceptionOnUIInit() throws Exception { openTestURL(); diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumnTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumnTest.java new file mode 100644 index 0000000000..ecb5285401 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/v7/tests/components/grid/RemoveHiddenColumnTest.java @@ -0,0 +1,45 @@ +package com.vaadin.v7.tests.components.grid; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class RemoveHiddenColumnTest extends SingleBrowserTest { + + @Test + public void removeHiddenColumnInEmptyGrid() { + openTestURL("debug"); + removeColumns(); + } + + @Test + public void removeHiddenColumnInPopulatedGrid() { + openTestURL("debug"); + ButtonElement add = $(ButtonElement.class).id("add"); + add.click(); + removeColumns(); + + } + + private void removeColumns() { + ButtonElement remove = $(ButtonElement.class).id("remove"); + remove.click(); + Assert.assertEquals("1. Removed column 'First Name' (hidden)", + getLogRow(0)); + assertNoErrorNotifications(); + + remove.click(); + Assert.assertEquals("2. Removed column 'Last Name'", getLogRow(0)); + assertNoErrorNotifications(); + remove.click(); + Assert.assertEquals("3. Removed column 'Email' (hidden)", getLogRow(0)); + assertNoErrorNotifications(); + remove.click(); + Assert.assertEquals("4. Removed column 'Age'", getLogRow(0)); + assertNoErrorNotifications(); + + } + +} |