]> source.dussan.org Git - vaadin-framework.git/commitdiff
Modifies GridElement to support details (#16644)
authorHenrik Paul <henrik@vaadin.com>
Tue, 17 Feb 2015 09:51:11 +0000 (11:51 +0200)
committerHenrik Paul <henrik@vaadin.com>
Tue, 17 Feb 2015 14:40:44 +0000 (14:40 +0000)
Grid's SubPartAware logic was refactored, splitting it into both Grid
and Escalator. Also adds tests for grid details rows.

Change-Id: I4876a8a9a397eea35526e15f7e447c69b0d96983

client/src/com/vaadin/client/widgets/Escalator.java
client/src/com/vaadin/client/widgets/Grid.java
uitest/src/com/vaadin/testbench/elements/GridElement.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java [new file with mode: 0644]

index 6c6998277f1d1f01361b61d1845c2e1594b350d7..567262c6b26e1cffbab642bab266994c8b2a5b60 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.client.widgets;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -57,6 +58,7 @@ import com.vaadin.client.BrowserInfo;
 import com.vaadin.client.DeferredWorker;
 import com.vaadin.client.Profiler;
 import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.ui.SubPartAware;
 import com.vaadin.client.widget.escalator.Cell;
 import com.vaadin.client.widget.escalator.ColumnConfiguration;
 import com.vaadin.client.widget.escalator.EscalatorUpdater;
@@ -267,7 +269,8 @@ abstract class JsniWorkaround {
  * @since 7.4
  * @author Vaadin Ltd
  */
-public class Escalator extends Widget implements RequiresResize, DeferredWorker {
+public class Escalator extends Widget implements RequiresResize,
+        DeferredWorker, SubPartAware {
 
     // todo comments legend
     /*
@@ -4319,6 +4322,26 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
             assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching";
             assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching";
         }
+
+        public String getSubPartName(Element subElement) {
+            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
+                if (spacer.getRootElement().isOrHasChild(subElement)) {
+                    return "spacer[" + spacer.getRow() + "]";
+                }
+            }
+            return null;
+        }
+
+        public Element getSubPartElement(int index) {
+            getLogger().warning("SpacerContainer.getSubPartElement " + index);
+
+            SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index));
+            if (spacer != null) {
+                return spacer.getElement();
+            } else {
+                return null;
+            }
+        }
     }
 
     private class ElementPositionBookkeeper {
@@ -4347,6 +4370,50 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
         }
     }
 
+    public static class SubPartArguments {
+        private String type;
+        private int[] indices;
+
+        private SubPartArguments(String type, int[] indices) {
+            /*
+             * The constructor is private so that no third party would by
+             * mistake start using this parsing scheme, since it's not official
+             * by TestBench (yet?).
+             */
+
+            this.type = type;
+            this.indices = indices;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public int getIndicesLength() {
+            return indices.length;
+        }
+
+        public int getIndex(int i) {
+            return indices[i];
+        }
+
+        public int[] getIndices() {
+            return Arrays.copyOf(indices, indices.length);
+        }
+
+        static SubPartArguments create(String subPart) {
+            String[] splitArgs = subPart.split("\\[");
+            String type = splitArgs[0];
+            int[] indices = new int[splitArgs.length - 1];
+            for (int i = 0; i < indices.length; ++i) {
+                String tmp = splitArgs[i + 1];
+                indices[i] = Integer
+                        .parseInt(tmp.substring(0, tmp.length() - 1));
+            }
+            return new SubPartArguments(type, indices);
+        }
+    }
+
     // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y
     /**
      * The solution to
@@ -5295,4 +5362,175 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
         int to = (int) Math.ceil(body.heightOfSection);
         return Range.between(from, to);
     }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
+        SubPartArguments args = parseSubPartArguments(subPart);
+
+        Element tableStructureElement = getSubPartElementTableStructure(args);
+        if (tableStructureElement != null) {
+            return DOM.asOld(tableStructureElement);
+        }
+
+        Element spacerElement = getSubPartElementSpacer(args);
+        if (spacerElement != null) {
+            return DOM.asOld(spacerElement);
+        }
+
+        return null;
+    }
+
+    private Element getSubPartElementTableStructure(SubPartArguments args) {
+
+        String type = args.getType();
+        int[] indices = args.getIndices();
+
+        // Get correct RowContainer for type from Escalator
+        RowContainer container = null;
+        if (type.equalsIgnoreCase("header")) {
+            container = getHeader();
+        } else if (type.equalsIgnoreCase("cell")) {
+            // If wanted row is not visible, we need to scroll there.
+            Range visibleRowRange = getVisibleRowRange();
+            if (indices.length > 0 && !visibleRowRange.contains(indices[0])) {
+                try {
+                    scrollToRow(indices[0], ScrollDestination.ANY, 0);
+                } catch (IllegalArgumentException e) {
+                    getLogger().log(Level.SEVERE, e.getMessage());
+                }
+                // Scrolling causes a lazy loading event. No element can
+                // currently be retrieved.
+                return null;
+            }
+            container = getBody();
+        } else if (type.equalsIgnoreCase("footer")) {
+            container = getFooter();
+        }
+
+        if (null != container) {
+            if (indices.length == 0) {
+                // No indexing. Just return the wanted container element
+                return container.getElement();
+            } else {
+                try {
+                    return getSubPart(container, indices);
+                } catch (Exception e) {
+                    getLogger().log(Level.SEVERE, e.getMessage());
+                }
+            }
+        }
+        return null;
+    }
+
+    private Element getSubPart(RowContainer container, int[] indices) {
+        Element targetElement = container.getRowElement(indices[0]);
+
+        // Scroll wanted column to view if able
+        if (indices.length > 1 && targetElement != null) {
+            if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) {
+                scrollToColumn(indices[1], ScrollDestination.ANY, 0);
+            }
+
+            targetElement = getCellFromRow(TableRowElement.as(targetElement),
+                    indices[1]);
+
+            for (int i = 2; i < indices.length && targetElement != null; ++i) {
+                targetElement = (Element) targetElement.getChild(indices[i]);
+            }
+        }
+
+        return targetElement;
+    }
+
+    private static Element getCellFromRow(TableRowElement rowElement, int index) {
+        int childCount = rowElement.getCells().getLength();
+        if (index < 0 || index >= childCount) {
+            return null;
+        }
+
+        TableCellElement currentCell = null;
+        boolean indexInColspan = false;
+        int i = 0;
+
+        while (!indexInColspan) {
+            currentCell = rowElement.getCells().getItem(i);
+
+            // Calculate if this is the cell we are looking for
+            int colSpan = currentCell.getColSpan();
+            indexInColspan = index < colSpan + i;
+
+            // Increment by colspan to skip over hidden cells
+            i += colSpan;
+        }
+        return currentCell;
+    }
+
+    private Element getSubPartElementSpacer(SubPartArguments args) {
+        if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) {
+            return body.spacerContainer.getSubPartElement(args.getIndex(0));
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
+
+        /*
+         * The spacer check needs to be before table structure check, because
+         * (for now) the table structure will take spacer elements into account
+         * as well, when it shouldn't.
+         */
+
+        String spacer = getSubPartNameSpacer(subElement);
+        if (spacer != null) {
+            return spacer;
+        }
+
+        String tableStructure = getSubPartNameTableStructure(subElement);
+        if (tableStructure != null) {
+            return tableStructure;
+        }
+
+        return null;
+    }
+
+    private String getSubPartNameTableStructure(Element subElement) {
+
+        List<RowContainer> containers = Arrays.asList(getHeader(), getBody(),
+                getFooter());
+        List<String> containerType = Arrays.asList("header", "cell", "footer");
+
+        for (int i = 0; i < containers.size(); ++i) {
+            RowContainer container = containers.get(i);
+            boolean containerRow = (subElement.getTagName().equalsIgnoreCase(
+                    "tr") && subElement.getParentElement() == container
+                    .getElement());
+            if (containerRow) {
+                /*
+                 * Wanted SubPart is row that is a child of containers root to
+                 * get indices, we use a cell that is a child of this row
+                 */
+                subElement = subElement.getFirstChildElement();
+            }
+
+            Cell cell = container.getCell(subElement);
+            if (cell != null) {
+                // Skip the column index if subElement was a child of root
+                return containerType.get(i) + "[" + cell.getRow()
+                        + (containerRow ? "]" : "][" + cell.getColumn() + "]");
+            }
+        }
+        return null;
+    }
+
+    private String getSubPartNameSpacer(Element subElement) {
+        return body.spacerContainer.getSubPartName(subElement);
+    }
+
+    public static SubPartArguments parseSubPartArguments(String subPart) {
+        return SubPartArguments.create(subPart);
+    }
 }
index 31984f7d5b22811ea40ad41840764f5f56e4d024..7890b139042389e0ae8eab978423556e727cd93c 100644 (file)
@@ -130,6 +130,7 @@ import com.vaadin.client.widget.grid.sort.SortEvent;
 import com.vaadin.client.widget.grid.sort.SortHandler;
 import com.vaadin.client.widget.grid.sort.SortOrder;
 import com.vaadin.client.widgets.Escalator.AbstractRowContainer;
+import com.vaadin.client.widgets.Escalator.SubPartArguments;
 import com.vaadin.client.widgets.Grid.Editor.State;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.grid.GridConstants;
@@ -5250,153 +5251,77 @@ public class Grid<T> extends ResizeComposite implements
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
-        // Parse SubPart string to type and indices
-        String[] splitArgs = subPart.split("\\[");
-
-        String type = splitArgs[0];
-        int[] indices = new int[splitArgs.length - 1];
-        for (int i = 0; i < indices.length; ++i) {
-            String tmp = splitArgs[i + 1];
-            indices[i] = Integer.parseInt(tmp.substring(0, tmp.length() - 1));
-        }
-
-        // Get correct RowContainer for type from Escalator
-        RowContainer container = null;
-        if (type.equalsIgnoreCase("header")) {
-            container = escalator.getHeader();
-        } else if (type.equalsIgnoreCase("cell")) {
-            // If wanted row is not visible, we need to scroll there.
-            Range visibleRowRange = escalator.getVisibleRowRange();
-            if (indices.length > 0 && !visibleRowRange.contains(indices[0])) {
-                try {
-                    scrollToRow(indices[0]);
-                } catch (IllegalArgumentException e) {
-                    getLogger().log(Level.SEVERE, e.getMessage());
-                }
-                // Scrolling causes a lazy loading event. No element can
-                // currently be retrieved.
-                return null;
-            }
-            container = escalator.getBody();
-        } else if (type.equalsIgnoreCase("footer")) {
-            container = escalator.getFooter();
-        } else if (type.equalsIgnoreCase("editor")) {
-            if (editor.getState() != State.ACTIVE) {
-                // Editor is not there.
-                return null;
-            }
 
-            if (indices.length == 0) {
-                return DOM.asOld(editor.editorOverlay);
-            } else if (indices.length == 1 && indices[0] < columns.size()) {
-                escalator.scrollToColumn(indices[0], ScrollDestination.ANY, 0);
-                return editor.getWidget(columns.get(indices[0])).getElement();
-            } else {
-                return null;
-            }
+        Element subPartElement = escalator.getSubPartElement(subPart
+                .replaceFirst("^details\\[", "spacer["));
+        if (subPartElement != null) {
+            return DOM.asOld(subPartElement);
         }
 
-        if (null != container) {
-            if (indices.length == 0) {
-                // No indexing. Just return the wanted container element
-                return DOM.asOld(container.getElement());
-            } else {
-                try {
-                    return DOM.asOld(getSubPart(container, indices));
-                } catch (Exception e) {
-                    getLogger().log(Level.SEVERE, e.getMessage());
-                }
-            }
+        SubPartArguments args = Escalator.parseSubPartArguments(subPart);
+
+        Element editor = getSubPartElementEditor(args);
+        if (editor != null) {
+            return DOM.asOld(editor);
         }
+
         return null;
     }
 
-    private Element getSubPart(RowContainer container, int[] indices) {
-        Element targetElement = container.getRowElement(indices[0]);
-
-        // Scroll wanted column to view if able
-        if (indices.length > 1 && targetElement != null) {
-            if (escalator.getColumnConfiguration().getFrozenColumnCount() <= indices[1]) {
-                escalator.scrollToColumn(indices[1], ScrollDestination.ANY, 0);
-            }
+    private Element getSubPartElementEditor(SubPartArguments args) {
 
-            targetElement = getCellFromRow(TableRowElement.as(targetElement),
-                    indices[1]);
-
-            for (int i = 2; i < indices.length && targetElement != null; ++i) {
-                targetElement = (Element) targetElement.getChild(indices[i]);
-            }
+        if (!args.getType().equalsIgnoreCase("editor")
+                || editor.getState() != State.ACTIVE) {
+            return null;
         }
 
-        return targetElement;
-    }
-
-    private Element getCellFromRow(TableRowElement rowElement, int index) {
-        int childCount = rowElement.getCells().getLength();
-        if (index < 0 || index >= childCount) {
-            return null;
+        if (args.getIndicesLength() == 0) {
+            return editor.editorOverlay;
+        } else if (args.getIndicesLength() == 1
+                && args.getIndex(0) < columns.size()) {
+            escalator
+                    .scrollToColumn(args.getIndex(0), ScrollDestination.ANY, 0);
+            return editor.getWidget(columns.get(args.getIndex(0))).getElement();
         }
 
-        TableCellElement currentCell = null;
-        boolean indexInColspan = false;
-        int i = 0;
+        return null;
+    }
 
-        while (!indexInColspan) {
-            currentCell = rowElement.getCells().getItem(i);
+    @Override
+    @SuppressWarnings("deprecation")
+    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
 
-            // Calculate if this is the cell we are looking for
-            int colSpan = currentCell.getColSpan();
-            indexInColspan = index < colSpan + i;
+        String escalatorStructureName = escalator.getSubPartName(subElement);
+        if (escalatorStructureName != null) {
+            return escalatorStructureName.replaceFirst("^spacer", "details");
+        }
 
-            // Increment by colspan to skip over hidden cells
-            i += colSpan;
+        String editorName = getSubPartNameEditor(subElement);
+        if (editorName != null) {
+            return editorName;
         }
-        return currentCell;
-    }
 
-    @Override
-    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
-        // Containers and matching SubPart types
-        List<RowContainer> containers = Arrays.asList(escalator.getHeader(),
-                escalator.getBody(), escalator.getFooter());
-        List<String> containerType = Arrays.asList("header", "cell", "footer");
+        return null;
+    }
 
-        for (int i = 0; i < containers.size(); ++i) {
-            RowContainer container = containers.get(i);
-            boolean containerRow = (subElement.getTagName().equalsIgnoreCase(
-                    "tr") && subElement.getParentElement() == container
-                    .getElement());
-            if (containerRow) {
-                // Wanted SubPart is row that is a child of containers root
-                // To get indices, we use a cell that is a child of this row
-                subElement = DOM.asOld(subElement.getFirstChildElement());
-            }
+    private String getSubPartNameEditor(Element subElement) {
 
-            Cell cell = container.getCell(subElement);
-            if (cell != null) {
-                // Skip the column index if subElement was a child of root
-                return containerType.get(i) + "[" + cell.getRow()
-                        + (containerRow ? "]" : "][" + cell.getColumn() + "]");
-            }
+        if (editor.getState() != State.ACTIVE
+                || !editor.editorOverlay.isOrHasChild(subElement)) {
+            return null;
         }
 
-        // Check if subelement is part of editor.
-        if (editor.getState() == State.ACTIVE) {
-            if (editor.editorOverlay.isOrHasChild(subElement)) {
-                int i = 0;
-                for (Column<?, T> column : columns) {
-                    if (editor.getWidget(column).getElement()
-                            .isOrHasChild(subElement)) {
-                        return "editor[" + i + "]";
-                    }
-                    ++i;
-                }
-                return "editor";
+        int i = 0;
+        for (Column<?, T> column : columns) {
+            if (editor.getWidget(column).getElement().isOrHasChild(subElement)) {
+                return "editor[" + i + "]";
             }
+            ++i;
         }
 
-        return null;
+        return "editor";
     }
 
     private void setSelectColumnRenderer(
index 3753696855d77fd14d6e4edca58b39dc33bbff14..ff7343a3d6ec75b19288f5d0d6a6b86a0edb4ae5 100644 (file)
@@ -25,7 +25,7 @@ import com.vaadin.testbench.By;
 import com.vaadin.testbench.TestBenchElement;
 
 /**
- * TestBench Element API for Grid
+ * TestBench Element API for Grid.
  * 
  * @since
  * @author Vaadin Ltd
@@ -148,7 +148,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Scrolls Grid element so that wanted row is displayed
+     * Scrolls Grid element so that wanted row is displayed.
      * 
      * @param index
      *            Target row
@@ -251,7 +251,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get header row count
+     * Get header row count.
      * 
      * @return Header row count
      */
@@ -260,7 +260,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get footer row count
+     * Get footer row count.
      * 
      * @return Footer row count
      */
@@ -269,7 +269,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get a header row by index
+     * Get a header row by index.
      * 
      * @param rowIndex
      *            Row index
@@ -280,7 +280,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get a footer row by index
+     * Get a footer row by index.
      * 
      * @param rowIndex
      *            Row index
@@ -291,7 +291,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the vertical scroll element
+     * Get the vertical scroll element.
      * 
      * @return The element representing the vertical scrollbar
      */
@@ -301,7 +301,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the horizontal scroll element
+     * Get the horizontal scroll element.
      * 
      * @return The element representing the horizontal scrollbar
      */
@@ -311,7 +311,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the header element
+     * Get the header element.
      * 
      * @return The thead element
      */
@@ -320,7 +320,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the body element
+     * Get the body element.
      * 
      * @return the tbody element
      */
@@ -329,7 +329,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the footer element
+     * Get the footer element.
      * 
      * @return the tfoot element
      */
@@ -338,7 +338,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Get the element wrapping the table element
+     * Get the element wrapping the table element.
      * 
      * @return The element that wraps the table element
      */
@@ -353,7 +353,7 @@ public class GridElement extends AbstractComponentElement {
     }
 
     /**
-     * Helper function to get Grid subparts wrapped correctly
+     * Helper function to get Grid subparts wrapped correctly.
      * 
      * @param subPartSelector
      *            SubPart to be used in ComponentLocator
@@ -362,4 +362,16 @@ public class GridElement extends AbstractComponentElement {
     private TestBenchElement getSubPart(String subPartSelector) {
         return (TestBenchElement) findElement(By.vaadin(subPartSelector));
     }
+
+    /**
+     * Gets the element that contains the details of a row.
+     * 
+     * @since
+     * @param rowIndex
+     *            the index of the row for the details
+     * @return the element that contains the details of a row
+     */
+    public TestBenchElement getDetails(int rowIndex) {
+        return getSubPart("#details[" + rowIndex + "]");
+    }
 }
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java
new file mode 100644 (file)
index 0000000..981a1cc
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid.basicfeatures.client;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.NoSuchElementException;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest;
+
+public class GridDetailsTest extends GridBasicClientFeaturesTest {
+
+    private static final String[] SET_GENERATOR = new String[] { "Component",
+            "Row details", "Set generator" };
+    private static final String[] TOGGLE_DETAILS_FOR_ROW_1 = new String[] {
+            "Component", "Row details", "Toggle details for row 1" };
+    private static final String[] TOGGLE_DETAILS_FOR_ROW_100 = new String[] {
+            "Component", "Row details", "Toggle details for row 100" };
+
+    @Before
+    public void setUp() {
+        openTestURL();
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void noDetailsByDefault() {
+        assertNull("details for row 1 should not exist at the start",
+                getGridElement().getDetails(1));
+    }
+
+    @Test
+    public void nullRendererShowsDetailsPlaceholder() {
+        selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1);
+        TestBenchElement details = getGridElement().getDetails(1);
+        assertNotNull("details for row 1 should not exist at the start",
+                details);
+        assertTrue("details should've been empty for null renderer", details
+                .getText().isEmpty());
+    }
+
+    @Test
+    public void applyRendererThenOpenDetails() {
+        selectMenuPath(SET_GENERATOR);
+        selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1);
+
+        TestBenchElement details = getGridElement().getDetails(1);
+        assertTrue("Unexpected details content",
+                details.getText().startsWith("Row: 1."));
+    }
+
+    @Test
+    public void openDetailsThenAppyRenderer() {
+        selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1);
+        selectMenuPath(SET_GENERATOR);
+
+        TestBenchElement details = getGridElement().getDetails(1);
+        assertTrue("Unexpected details content",
+                details.getText().startsWith("Row: 1."));
+    }
+
+    @Test
+    public void openHiddenDetailsThenScrollToIt() {
+        try {
+            getGridElement().getDetails(100);
+            fail("details row for 100 was apparently found, while it shouldn't have been.");
+        } catch (NoSuchElementException e) {
+            // expected
+        }
+
+        selectMenuPath(SET_GENERATOR);
+        selectMenuPath(TOGGLE_DETAILS_FOR_ROW_100);
+
+        // scroll a bit beyond so we see below.
+        getGridElement().scrollToRow(101);
+
+        TestBenchElement details = getGridElement().getDetails(100);
+        assertTrue("Unexpected details content",
+                details.getText().startsWith("Row: 100."));
+    }
+}