]> source.dussan.org Git - vaadin-framework.git/commitdiff
Tweaks to Grid/Escalator column size handling (#12145) (#12157)
authorAnna Koskinen <Ansku@users.noreply.github.com>
Tue, 1 Dec 2020 12:46:27 +0000 (14:46 +0200)
committerGitHub <noreply@github.com>
Tue, 1 Dec 2020 12:46:27 +0000 (14:46 +0200)
- ScrollbarBundle: removed delays in scroll handling that were only
needed for IE8, added possibility to update offsetSize and scrollSize at
the same time in order to avoid triggering unnecessary scrollbar
visibility change events during the intermediate state.
- ColumnConfigurator: added new method that allows setting column widths
without triggering element size recalculations.
- EscalatorProxy: added implementation of the new method to
ColumnConfigurationProxy.
- Escalator: switched to use new methods in ScrollbarBundle and
ColumnConfigurator, added a pixel to a scrollbar offsetSize calculation
that was for some reason consistently one pixel too low, removed
duplicate method calls from sectionHeightCalculated handling as those
are already handled by the calling method and can cause incorrect
intermediate state and unnecessary scrollbar visibility change events,
added implementation of the new method to ColumnConfigurationImpl with
the element size recalculations made optional.
- Grid: updated column minimum width calculations to take into account
the potential presence of a resize handle, updated expand ratio handling
to not trigger element size recalculations until the entire handling is
finished.
- Test for column width handling when there are multiple columns with
setMinimumWidthFromContent(false)

Fixes #12139

client/src/main/java/com/vaadin/client/widget/escalator/ColumnConfiguration.java
client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java
client/src/main/java/com/vaadin/client/widgets/Escalator.java
client/src/main/java/com/vaadin/client/widgets/Grid.java
uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContent.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java
uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContentTest.java [new file with mode: 0644]

index 51e86637491d6540c00afec7edba80b61504902e..8488981a764a7d79c262cf2e6c58a131ac1df122 100644 (file)
@@ -143,7 +143,8 @@ public interface ColumnConfiguration {
     public double getColumnWidth(int index) throws IllegalArgumentException;
 
     /**
-     * Sets widths for a set of columns.
+     * Sets widths for a set of columns. Triggers element size recalculation for
+     * elements that require manual calculations.
      *
      * @param indexWidthMap
      *            a map from column index to its respective width to be set. If
@@ -159,6 +160,26 @@ public interface ColumnConfiguration {
     public void setColumnWidths(Map<Integer, Double> indexWidthMap)
             throws IllegalArgumentException;
 
+    /**
+     * Sets widths for a set of columns.
+     *
+     * @param indexWidthMap
+     *            a map from column index to its respective width to be set. If
+     *            the given width for a column index is negative, the column is
+     *            resized-to-fit.
+     * @param recalculateElementSizes
+     *            should the element size recalculation be triggered for
+     *            elements that require manual calculation
+     * @throws IllegalArgumentException
+     *             if {@code indexWidthMap} is {@code null}
+     * @throws IllegalArgumentException
+     *             if any column index in {@code indexWidthMap} is invalid
+     * @throws NullPointerException
+     *             If any value in the map is <code>null</code>
+     */
+    public void setColumnWidths(Map<Integer, Double> indexWidthMap,
+            boolean recalculateElementSizes) throws IllegalArgumentException;
+
     /**
      * Returns the actual width of a column.
      *
index 122c302f362f8599a50433f620a39f73a80fa0da..3a01c267fc45f8035bc3b56331593f76b35a4fd7 100644 (file)
@@ -354,8 +354,6 @@ public abstract class ScrollbarBundle implements DeferredWorker {
 
     private final ScrollEventFirer scrollEventFirer = new ScrollEventFirer();
 
-    private HandlerRegistration scrollSizeTemporaryScrollHandler;
-    private HandlerRegistration offsetSizeTemporaryScrollHandler;
     private HandlerRegistration scrollInProgress;
 
     private ScrollbarBundle() {
@@ -417,27 +415,18 @@ public abstract class ScrollbarBundle implements DeferredWorker {
      *
      * @param px
      *            the length of the scrollbar in pixels
+     * @see #setOffsetSizeAndScrollSize(double, double)
      */
     public final void setOffsetSize(final double px) {
 
-        /*
-         * This needs to be made step-by-step because IE8 flat-out refuses to
-         * fire a scroll event when the scroll size becomes smaller than the
-         * offset size. All other browser need to suffer alongside.
-         */
-
         boolean newOffsetSizeIsGreaterThanScrollSize = px > getScrollSize();
         boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle()
                 && newOffsetSizeIsGreaterThanScrollSize;
+
         if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) {
-            if (offsetSizeTemporaryScrollHandler != null) {
-                offsetSizeTemporaryScrollHandler.removeHandler();
-            }
-            // must be a field because Java insists.
-            offsetSizeTemporaryScrollHandler = addScrollHandler(
-                    event -> setOffsetSizeNow(px));
             setScrollPos(0);
-        } else {
+            setOffsetSizeNow(px);
+        } else if (px != getOffsetSize()) {
             setOffsetSizeNow(px);
         }
     }
@@ -447,9 +436,50 @@ public abstract class ScrollbarBundle implements DeferredWorker {
         recalculateMaxScrollPos();
         forceScrollbar(showsScrollHandle());
         fireVisibilityChangeIfNeeded();
-        if (offsetSizeTemporaryScrollHandler != null) {
-            offsetSizeTemporaryScrollHandler.removeHandler();
-            offsetSizeTemporaryScrollHandler = null;
+    }
+
+    /**
+     * Sets the length of the scrollbar and the amount of pixels the scrollbar
+     * needs to be able to scroll through.
+     *
+     * @param offsetPx
+     *            the length of the scrollbar in pixels
+     * @param scrollPx
+     *            the number of pixels the scrollbar should be able to scroll
+     *            through
+     */
+    public final void setOffsetSizeAndScrollSize(final double offsetPx,
+            final double scrollPx) {
+
+        boolean newOffsetSizeIsGreaterThanScrollSize = offsetPx > scrollPx;
+        boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle()
+                && newOffsetSizeIsGreaterThanScrollSize;
+
+        boolean needsMoreHandling = false;
+        if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) {
+            setScrollPos(0);
+            if (offsetPx != getOffsetSize()) {
+                internalSetOffsetSize(Math.max(0, offsetPx));
+            }
+            if (scrollPx != getScrollSize()) {
+                internalSetScrollSize(Math.max(0, scrollPx));
+            }
+            needsMoreHandling = true;
+        } else {
+            if (offsetPx != getOffsetSize()) {
+                internalSetOffsetSize(Math.max(0, offsetPx));
+                needsMoreHandling = true;
+            }
+            if (scrollPx != getScrollSize()) {
+                internalSetScrollSize(Math.max(0, scrollPx));
+                needsMoreHandling = true;
+            }
+        }
+
+        if (needsMoreHandling) {
+            recalculateMaxScrollPos();
+            forceScrollbar(showsScrollHandle());
+            fireVisibilityChangeIfNeeded();
         }
     }
 
@@ -626,43 +656,18 @@ public abstract class ScrollbarBundle implements DeferredWorker {
      * @param px
      *            the number of pixels the scrollbar should be able to scroll
      *            through
+     * @see #setOffsetSizeAndScrollSize(double, double)
      */
     public final void setScrollSize(final double px) {
 
-        /*
-         * This needs to be made step-by-step because IE8 flat-out refuses to
-         * fire a scroll event when the scroll size becomes smaller than the
-         * offset size. All other browser need to suffer alongside.
-         *
-         * This really should be changed to not use any temporary scroll
-         * handlers at all once IE8 support is dropped, like now done only for
-         * Firefox.
-         */
-
         boolean newScrollSizeIsSmallerThanOffsetSize = px <= getOffsetSize();
         boolean scrollSizeBecomesSmallerThanOffsetSize = showsScrollHandle()
                 && newScrollSizeIsSmallerThanOffsetSize;
+
         if (scrollSizeBecomesSmallerThanOffsetSize && getScrollPos() != 0) {
-            /*
-             * For whatever reason, Firefox loses the scroll event in this case
-             * and the onscroll handler is never called (happens when reducing
-             * size from 1000 items to 1 while being scrolled a bit down, see
-             * #19802). Based on the comment above, only IE8 should really use
-             * 'delayedSizeSet'
-             */
-            boolean delayedSizeSet = !BrowserInfo.get().isFirefox();
-            if (delayedSizeSet) {
-                if (scrollSizeTemporaryScrollHandler != null) {
-                    scrollSizeTemporaryScrollHandler.removeHandler();
-                }
-                scrollSizeTemporaryScrollHandler = addScrollHandler(
-                        event -> setScrollSizeNow(px));
-            }
             setScrollPos(0);
-            if (!delayedSizeSet) {
-                setScrollSizeNow(px);
-            }
-        } else {
+            setScrollSizeNow(px);
+        } else if (px != getScrollSize()) {
             setScrollSizeNow(px);
         }
     }
@@ -672,10 +677,6 @@ public abstract class ScrollbarBundle implements DeferredWorker {
         recalculateMaxScrollPos();
         forceScrollbar(showsScrollHandle());
         fireVisibilityChangeIfNeeded();
-        if (scrollSizeTemporaryScrollHandler != null) {
-            scrollSizeTemporaryScrollHandler.removeHandler();
-            scrollSizeTemporaryScrollHandler = null;
-        }
     }
 
     /**
@@ -913,8 +914,6 @@ public abstract class ScrollbarBundle implements DeferredWorker {
     public boolean isWorkPending() {
         // Need to include scrollEventFirer.isBeingFired as it might use
         // requestAnimationFrame - which is not automatically checked
-        return scrollSizeTemporaryScrollHandler != null
-                || offsetSizeTemporaryScrollHandler != null
-                || scrollInProgress != null || scrollEventFirer.isBeingFired;
+        return scrollInProgress != null || scrollEventFirer.isBeingFired;
     }
 }
index 61dbbf6dd9438f9a97ede348c762a7f20a53bb67..a5046ce970d0bb107e0031d357513cb29b5545b8 100644 (file)
@@ -867,8 +867,8 @@ public class Escalator extends Widget
             double headerHeight = header.getHeightOfSection();
             double vScrollbarHeight = Math.max(0,
                     tableWrapperHeight - footerHeight - headerHeight);
-            verticalScrollbar.setOffsetSize(vScrollbarHeight);
-            verticalScrollbar.setScrollSize(scrollContentHeight);
+            verticalScrollbar.setOffsetSizeAndScrollSize(vScrollbarHeight,
+                    scrollContentHeight);
 
             /*
              * If decreasing the amount of frozen columns, and scrolled to the
@@ -884,8 +884,8 @@ public class Escalator extends Widget
                             columnConfiguration.getColumnCount()));
             double frozenPixels = scrollContentWidth - unfrozenPixels;
             double hScrollOffsetWidth = tableWrapperWidth - frozenPixels;
-            horizontalScrollbar.setOffsetSize(hScrollOffsetWidth);
-            horizontalScrollbar.setScrollSize(unfrozenPixels);
+            horizontalScrollbar.setOffsetSizeAndScrollSize(hScrollOffsetWidth,
+                    unfrozenPixels);
             horizontalScrollbar.getElement().getStyle().setLeft(frozenPixels,
                     Unit.PX);
             horizontalScrollbar.setScrollPos(prevScrollPos);
@@ -1524,7 +1524,8 @@ public class Escalator extends Widget
                             Integer col = Integer.valueOf(i);
                             colWidths.put(col, width);
                         }
-                        getColumnConfiguration().setColumnWidths(colWidths);
+                        getColumnConfiguration().setColumnWidths(colWidths,
+                                true);
                     });
                 }
             }
@@ -2486,7 +2487,7 @@ public class Escalator extends Widget
                  */
                 verticalScrollbar.setOffsetSize(
                         heightOfEscalator - header.getHeightOfSection()
-                                - footer.getHeightOfSection());
+                                - footer.getHeightOfSection() + 1);
 
                 body.verifyEscalatorCount();
                 body.spacerContainer.updateSpacerDecosVisibility();
@@ -2604,21 +2605,8 @@ public class Escalator extends Widget
 
         @Override
         protected void sectionHeightCalculated() {
-            double headerHeight = header.getHeightOfSection();
-            double footerHeight = footer.getHeightOfSection();
-            int vscrollHeight = (int) Math
-                    .floor(heightOfEscalator - headerHeight - footerHeight);
-
-            final boolean horizontalScrollbarNeeded = columnConfiguration
-                    .calculateRowWidth() > widthOfEscalator;
-            if (horizontalScrollbarNeeded) {
-                vscrollHeight -= horizontalScrollbar.getScrollbarThickness();
-            }
-
             footerDeco.getStyle().setHeight(footer.getHeightOfSection(),
                     Unit.PX);
-
-            verticalScrollbar.setOffsetSize(vscrollHeight);
         }
     }
 
@@ -5745,7 +5733,7 @@ public class Escalator extends Widget
                     Integer col = Integer.valueOf(i);
                     colWidths.put(col, width);
                 }
-                getColumnConfiguration().setColumnWidths(colWidths);
+                getColumnConfiguration().setColumnWidths(colWidths, true);
             }
 
             // Adjust scrollbar
@@ -5839,12 +5827,19 @@ public class Escalator extends Widget
         public void setColumnWidth(int index, double px)
                 throws IllegalArgumentException {
             setColumnWidths(Collections.singletonMap(Integer.valueOf(index),
-                    Double.valueOf(px)));
+                    Double.valueOf(px)), true);
         }
 
         @Override
         public void setColumnWidths(Map<Integer, Double> indexWidthMap)
                 throws IllegalArgumentException {
+            setColumnWidths(indexWidthMap, true);
+        }
+
+        @Override
+        public void setColumnWidths(Map<Integer, Double> indexWidthMap,
+                boolean recalculateElementSizes)
+                throws IllegalArgumentException {
 
             if (indexWidthMap == null) {
                 throw new IllegalArgumentException("indexWidthMap was null");
@@ -5874,7 +5869,9 @@ public class Escalator extends Widget
                 body.reapplyColumnWidths();
                 footer.reapplyColumnWidths();
 
-                recalculateElementSizes();
+                if (recalculateElementSizes) {
+                    recalculateElementSizes();
+                }
 
             } finally {
                 Profiler.leave(
index a47cc4bfcaf7616898283b0daeee0de130c8da8a..d4c93751ea7222a84ec2f40c69245d69fcff4f64 100755 (executable)
@@ -3462,10 +3462,27 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
         private boolean columnsAreGuaranteedToBeWiderThanGrid() {
             double freeSpace = escalator.getInnerWidth();
             for (Column<?, ?> column : getVisibleColumns()) {
+                /*
+                 * Check the width and min width and ensure that no column can
+                 * be expected to be narrower than what the resize handler
+                 * requires, if one is present.
+                 */
                 if (column.getWidth() >= 0) {
-                    freeSpace -= column.getWidth();
+                    if (column.isResizable() && resizeHandleWidth > 0) {
+                        freeSpace -= Math.max(resizeHandleWidth,
+                                column.getWidth());
+                    } else {
+                        freeSpace -= column.getWidth();
+                    }
                 } else if (column.getMinimumWidth() >= 0) {
-                    freeSpace -= column.getMinimumWidth();
+                    if (column.isResizable() && resizeHandleWidth > 0) {
+                        freeSpace -= Math.max(resizeHandleWidth,
+                                column.getMinimumWidth());
+                    } else {
+                        freeSpace -= column.getMinimumWidth();
+                    }
+                } else if (column.isResizable() && resizeHandleWidth > 0) {
+                    freeSpace -= resizeHandleWidth;
                 }
             }
             return freeSpace < 0;
@@ -3482,7 +3499,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
                 selfWidths.put(index, columns.get(index).getWidth());
             }
             Grid.this.escalator.getColumnConfiguration()
-                    .setColumnWidths(selfWidths);
+                    .setColumnWidths(selfWidths, true);
 
             /*
              * Step 2: Make sure that each column ends up obeying their min/max
@@ -3508,7 +3525,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
                 }
             }
             Grid.this.escalator.getColumnConfiguration()
-                    .setColumnWidths(constrainedWidths);
+                    .setColumnWidths(constrainedWidths, true);
         }
 
         private void applyColumnWidthsWithExpansion() {
@@ -3530,15 +3547,21 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             for (Column<?, T> column : visibleColumns) {
                 final double widthAsIs = column.getWidth();
                 final boolean isFixedWidth = widthAsIs >= 0;
-                // Check for max width just to be sure we don't break the limits
-                final double widthFixed = Math.max(
-                        Math.min(getMaxWidth(column), widthAsIs),
-                        column.getMinimumWidth());
                 defaultExpandRatios = defaultExpandRatios
                         && (column.getExpandRatio() == -1
                                 || column == selectionColumn);
 
                 if (isFixedWidth) {
+                    // Check for min & max width just to be sure we don't break
+                    // the limits
+                    double widthFixed = Math.max(
+                            Math.min(getMaxWidth(column), widthAsIs),
+                            column.getMinimumWidth());
+                    if (column.isResizable() && resizeHandleWidth > 0) {
+                        // Ensure the resize handle fits
+                        widthFixed = Math.max(widthFixed, resizeHandleWidth);
+                    }
+
                     columnSizes.put(visibleColumns.indexOf(column), widthFixed);
                     reservedPixels += widthFixed;
                 } else {
@@ -3547,7 +3570,13 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
                 }
             }
 
-            setColumnSizes(columnSizes);
+            /*
+             * Set column sizes so that it's possible to measure non-fixed
+             * actual sizes without previously applied expand ratio tweaks, but
+             * don't trigger the element size recalculation before the rest of
+             * this method has also been processed.
+             */
+            setColumnSizes(columnSizes, false);
 
             for (Column<?, T> column : nonFixedColumns) {
                 final int expandRatio = defaultExpandRatios ? 1
@@ -3555,9 +3584,21 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
                 final double maxWidth = getMaxWidth(column);
                 double newWidth;
                 if (column.isMinimumWidthFromContent()) {
-                    newWidth = Math.min(maxWidth, column.getWidthActual());
+                    if (column.isResizable() && resizeHandleWidth > 0) {
+                        // Ensure the resize handle fits
+                        newWidth = Math.max(
+                                Math.min(maxWidth, column.getWidthActual()),
+                                resizeHandleWidth);
+                    } else {
+                        newWidth = Math.min(maxWidth, column.getWidthActual());
+                    }
                 } else {
-                    newWidth = 0;
+                    if (column.isResizable() && resizeHandleWidth > 0) {
+                        // Ensure the resize handle fits
+                        newWidth = resizeHandleWidth;
+                    } else {
+                        newWidth = 0;
+                    }
                 }
 
                 boolean shouldExpand = newWidth < maxWidth && expandRatio > 0
@@ -3580,8 +3621,15 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             if (pixelsToDistribute <= 0 || totalRatios <= 0) {
                 if (pixelsToDistribute <= 0) {
                     // Set column sizes for expanding columns
-                    setColumnSizes(columnSizes);
+                    setColumnSizes(columnSizes, true);
                 }
+                /*
+                 * If pixelsToDistribute > 0 the element size recalculation
+                 * isn't done at all, even if some column sizes were set
+                 * earlier, but this doesn't appear to be detrimental while
+                 * attempting to trigger the recalculation here breaks a
+                 * GridEditRow test.
+                 */
 
                 return;
             }
@@ -3617,7 +3665,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             } while (aColumnHasMaxedOut);
 
             if (totalRatios <= 0 && columnsToExpand.isEmpty()) {
-                setColumnSizes(columnSizes);
+                setColumnSizes(columnSizes, true);
                 return;
             }
             assert pixelsToDistribute > 0 : "We've run out of pixels to distribute ("
@@ -3722,12 +3770,14 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             } while (minWidthsCausedReflows);
 
             // Finally set all the column sizes.
-            setColumnSizes(columnSizes);
+            setColumnSizes(columnSizes, true);
         }
 
-        private void setColumnSizes(Map<Integer, Double> columnSizes) {
+        private void setColumnSizes(Map<Integer, Double> columnSizes,
+                boolean recalculateElementSizes) {
             // Set all widths at once
-            escalator.getColumnConfiguration().setColumnWidths(columnSizes);
+            escalator.getColumnConfiguration().setColumnWidths(columnSizes,
+                    recalculateElementSizes);
         }
 
         private int getExpandRatio(Column<?, ?> column,
@@ -4389,6 +4439,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
 
     private boolean refreshBodyRequested = false;
 
+    private double resizeHandleWidth = 0;
+
     private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {
 
         private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() {
@@ -6056,6 +6108,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
                     final DragHandle dragger = new DragHandle(
                             getStylePrimaryName() + "-column-resize-handle");
                     dragger.addTo(td);
+                    // Save the newest resize handle's width with the assumption
+                    // that all the resize handles are the same size. This is
+                    // used in column's minimum width calculations, so the
+                    // border of the cell is also included.
+                    resizeHandleWidth = dragger.getElement().getOffsetWidth()
+                            + WidgetUtil.getBorderLeftAndRightThickness(td);
 
                     // Common functionality for drag handle callback
                     // implementations
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContent.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContent.java
new file mode 100644 (file)
index 0000000..e5a88f8
--- /dev/null
@@ -0,0 +1,66 @@
+package com.vaadin.tests.components.grid;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.components.grid.FooterRow;
+
+public class GridColumnsNoMinimumWidthFromContent extends AbstractTestUI {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        Random random = new Random();
+
+        List<DummyGridRow> gridRows = new ArrayList<DummyGridRow>();
+        gridRows.add(new DummyGridRow(random));
+
+        Grid<DummyGridRow> grid = new Grid<DummyGridRow>();
+        for (int i = 0; i < 20; i++) {
+            grid.addColumn(DummyGridRow::getValue)
+                    .setCaption("[" + i + "] Quite dummy column")
+                    .setMinimumWidthFromContent(false);
+        }
+
+        grid.setItems(gridRows);
+        FooterRow defaultFooter = grid.appendFooterRow();
+        grid.getColumns().forEach(column -> defaultFooter.getCell(column)
+                .setText(grid.getDefaultHeaderRow().getCell(column).getText()));
+        grid.setFooterVisible(true);
+        grid.setHeightByRows(gridRows.size());
+        grid.setWidthFull();
+
+        getLayout().addComponent(grid);
+    }
+
+    class DummyGridRow {
+        private Random random = null;
+
+        public DummyGridRow(Random random) {
+            this.random = random;
+        }
+
+        public int getValue() {
+            return random.nextInt(1000000000);
+        }
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 12139;
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Loading the UI should not get stuck in an eternal loop "
+                + "and the columns should be narrow with ellipsis "
+                + "until the page is resized small enough that "
+                + "the resize handles alone force a scrollbar. "
+                + "No overflowing of header cells should occur "
+                + "when resized very near to the cutoff point "
+                + "between no scrollbar and a scrollbar.";
+    }
+}
index 5d5148109b21ce055efc908f5ead8588db52ba25..65fe75e9fa857b01133fe07ebf80921a0d8ab60a 100644 (file)
@@ -86,6 +86,14 @@ public class EscalatorProxy extends Escalator {
                 throws IllegalArgumentException {
             columnConfiguration.setColumnWidths(indexWidthMap);
         }
+
+        @Override
+        public void setColumnWidths(Map<Integer, Double> indexWidthMap,
+                boolean recalculateElementSizes)
+                throws IllegalArgumentException {
+            columnConfiguration.setColumnWidths(indexWidthMap,
+                    recalculateElementSizes);
+        }
     }
 
     private class BodyRowContainerProxy extends RowContainerProxy
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContentTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnsNoMinimumWidthFromContentTest.java
new file mode 100644 (file)
index 0000000..6ae15fd
--- /dev/null
@@ -0,0 +1,59 @@
+package com.vaadin.tests.components.grid;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.number.IsCloseTo.closeTo;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class GridColumnsNoMinimumWidthFromContentTest extends MultiBrowserTest {
+
+    @Test
+    public void testResizing() {
+        openTestURL();
+
+        GridElement grid = $(GridElement.class).first();
+        WebElement hScrollbar = grid
+                .findElement(By.className("v-grid-scroller-horizontal"));
+
+        // initial state, should have no scrollbar
+        GridCellElement lastColumn = grid.getHeaderCell(0, 19);
+        ensureScrollbarVisibility(hScrollbar, false);
+        ensureNoGap(grid, lastColumn);
+
+        // resize small enough to get a scrollbar
+        getDriver().manage().window().setSize(new Dimension(810, 800));
+        ensureScrollbarVisibility(hScrollbar, true);
+
+        // resize just enough to lose the scrollbar
+        getDriver().manage().window().setSize(new Dimension(840, 800));
+        ensureScrollbarVisibility(hScrollbar, false);
+        ensureNoGap(grid, lastColumn);
+
+        int lastColumnWidth = lastColumn.getSize().getWidth();
+        assertGreater("Unexpected last column width: " + lastColumnWidth
+                + " (should be over 30)", lastColumnWidth, 30);
+    }
+
+    private void ensureNoGap(GridElement grid, GridCellElement lastColumn) {
+        int gridRightEdge = grid.getLocation().getX()
+                + grid.getSize().getWidth();
+        int lastColumnRightEdge = lastColumn.getLocation().getX()
+                + lastColumn.getSize().getWidth();
+        assertThat("Unexpected positioning.", (double) gridRightEdge,
+                closeTo(lastColumnRightEdge, 1d));
+    }
+
+    private void ensureScrollbarVisibility(WebElement scrollbar,
+            boolean displayed) {
+        assertEquals(displayed ? "block" : "none",
+                scrollbar.getCssValue("display"));
+    }
+}