]> source.dussan.org Git - vaadin-framework.git/commitdiff
Allow configuring content modes for Grid cell tooltips (#10396)
authorLeif Åstrand <legioth@gmail.com>
Tue, 6 Feb 2018 14:32:41 +0000 (16:32 +0200)
committerIlia Motornyi <elmot@vaadin.com>
Tue, 6 Feb 2018 14:32:41 +0000 (16:32 +0200)
client/src/main/java/com/vaadin/client/TooltipInfo.java
client/src/main/java/com/vaadin/client/VTooltip.java
client/src/main/java/com/vaadin/client/connectors/GridConnector.java
server/src/main/java/com/vaadin/ui/Grid.java
shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java
themes/src/main/themes/VAADIN/themes/base/common/common.scss
themes/src/main/themes/VAADIN/themes/valo/shared/_tooltip.scss
uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java

index 279b3b7686082cf38ab8d6d32fd44944e804c110..6667fd546a33e4dab292dca58f6bf66712178ac6 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.client;
 
 import com.vaadin.shared.ui.ErrorLevel;
+import com.vaadin.shared.ui.label.ContentMode;
 import com.vaadin.shared.util.SharedUtil;
 
 /**
@@ -30,6 +31,8 @@ public class TooltipInfo {
 
     private ErrorLevel errorLevel;
 
+    private ContentMode contentMode = ContentMode.HTML;
+
     // Contains the tooltip's identifier. If a tooltip's contents and this
     // identifier haven't changed, the tooltip won't be updated in subsequent
     // events.
@@ -203,4 +206,27 @@ public class TooltipInfo {
                 && SharedUtil.equals(other.errorLevel, errorLevel)
                 && other.identifier == identifier);
     }
+
+    /**
+     * Gets the tooltip title's content mode.
+     * 
+     * @since
+     *
+     * @return the content mode
+     */
+    public ContentMode getContentMode() {
+        return contentMode;
+    }
+
+    /**
+     * Sets the tooltip title's content mode.
+     *
+     * @since
+     * 
+     * @param contentMode
+     *            the content mode to set
+     */
+    public void setContentMode(ContentMode contentMode) {
+        this.contentMode = contentMode;
+    }
 }
index 64f36fda4760a48825dbdb392b4a8051f81b1ab2..6083f0c2f8a0840f49424eb6ff3b97454179c919 100644 (file)
@@ -18,7 +18,9 @@ package com.vaadin.client;
 import com.google.gwt.aria.client.LiveValue;
 import com.google.gwt.aria.client.RelevantValue;
 import com.google.gwt.aria.client.Roles;
+import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.PreElement;
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
@@ -48,8 +50,9 @@ import com.vaadin.client.ui.VOverlay;
 public class VTooltip extends VOverlay {
     private static final String CLASSNAME = "v-tooltip";
     private static final int MARGIN = 4;
-    public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN | Event.ONMOUSEOVER
-            | Event.ONMOUSEOUT | Event.ONMOUSEMOVE | Event.ONCLICK;
+    public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
+            | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEMOVE
+            | Event.ONCLICK;
     VErrorMessage em = new VErrorMessage();
     Element description = DOM.createDiv();
 
@@ -142,7 +145,29 @@ public class VTooltip extends VOverlay {
             em.setVisible(false);
         }
         if (info.getTitle() != null && !info.getTitle().isEmpty()) {
-            description.setInnerHTML(info.getTitle());
+            switch (info.getContentMode()) {
+            case RAW:
+            case XML:
+            case HTML:
+                description.setInnerHTML(info.getTitle());
+                break;
+            case TEXT:
+                // Cannot use setInnerText because IE8 preserves linebreaks
+                description.removeAllChildren();
+                description.appendChild(Document.get().createTextNode(
+                        info.getTitle()));
+                break;
+            case PREFORMATTED:
+                PreElement preElement = Document.get().createPreElement();
+                preElement.addClassName(CLASSNAME + "-pre");
+                preElement.setInnerText(info.getTitle());
+                description.removeAllChildren();
+                // add preformatted text to dom
+                description.appendChild(preElement);
+                break;
+            default:
+                break;
+            }
             /*
              * Issue #11871: to correctly update the offsetWidth of description
              * element we need to clear style width of its parent DIV from old
index 2805f8eb7b454a961d4587d99e0c868bb95db18a..d120a29fbff80d9f80cd0c5f909303856f805e13 100644 (file)
@@ -99,6 +99,7 @@ import com.vaadin.shared.ui.grid.GridStaticSectionState;
 import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
 import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
 import com.vaadin.shared.ui.grid.ScrollDestination;
+import com.vaadin.shared.ui.label.ContentMode;
 
 import elemental.json.JsonObject;
 import elemental.json.JsonValue;
@@ -1325,10 +1326,12 @@ public class GridConnector extends AbstractHasComponentsConnector
                     .getObject(GridState.JSONKEY_CELLDESCRIPTION);
 
             if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) {
-                return new TooltipInfo(cellDescriptions.getString(c.id));
+                return createCellTooltipInfo(cellDescriptions.getString(c.id),
+                        getState().cellTooltipContentMode);
             } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
-                return new TooltipInfo(
-                        row.getString(GridState.JSONKEY_ROWDESCRIPTION));
+                return createCellTooltipInfo(
+                        row.getString(GridState.JSONKEY_ROWDESCRIPTION),
+                        getState().rowTooltipContentMode);
             } else {
                 return null;
             }
@@ -1337,6 +1340,13 @@ public class GridConnector extends AbstractHasComponentsConnector
         return super.getTooltipInfo(element);
     }
 
+    private static TooltipInfo createCellTooltipInfo(String text,
+            ContentMode contentMode) {
+        TooltipInfo info = new TooltipInfo(text);
+        info.setContentMode(contentMode);
+        return info;
+    }
+
     @Override
     protected void sendContextClickEvent(MouseEventDetails details,
             EventTarget eventTarget) {
index 5bdb76938f70931c8c218b40b047d2c0949e19cf..a34af57c071fa548681ca315c53b444b3498fe23 100644 (file)
@@ -104,6 +104,7 @@ import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
 import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
 import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
 import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
+import com.vaadin.shared.ui.label.ContentMode;
 import com.vaadin.shared.util.SharedUtil;
 import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed;
 import com.vaadin.ui.declarative.DesignAttributeHandler;
@@ -6663,21 +6664,54 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
      * optional descriptions (tooltips) for individual Grid cells. If a
      * {@link RowDescriptionGenerator} is also set, the row description it
      * generates is displayed for cells for which {@code generator} returns
-     * null.
+     * <code>null</code>.
+     * <p>
+     * The descriptions are rendered in the browser as HTML and the developer is
+     * responsible for ensuring no harmful HTML is used.
      *
      * @param generator
-     *            the description generator to use or {@code null} to remove a
-     *            previously set generator if any
+     *            the description generator to use or <code>null</code> to
+     *            remove a previously set generator if any
      *
+     * @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
      * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
      *
      * @since 7.6
      */
-    public void setCellDescriptionGenerator(
-            CellDescriptionGenerator generator) {
+
+    public void setCellDescriptionGenerator(CellDescriptionGenerator generator) {
+        /*
+         * When porting this to the v7 version in Framework 8, the default
+         * should be changed to PREFORMATTED to preserve the more secure
+         * default that has accidentally been used there.
+         */
+        setCellDescriptionGenerator(generator, ContentMode.HTML);
+    }
+
+    /**
+     * Sets the {@code CellDescriptionGenerator} instance and content mode for
+     * generating optional descriptions (tooltips) for individual Grid cells. If
+     * a {@link RowDescriptionGenerator} is also set, the row description it
+     * generates is displayed for cells for which {@code generator} returns
+     * <code>null</code>.
+     *
+     * @param generator
+     *            the description generator to use or <code>null</code> to
+     *            remove a previously set generator if any
+     * @param contentMode
+     *            the content mode for cell tooltips, not <code>null</code>
+     * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
+     *
+     * @since
+     */
+    public void setCellDescriptionGenerator(CellDescriptionGenerator generator,
+            ContentMode contentMode) {
+        if (contentMode == null) {
+            throw new IllegalArgumentException("Content mode cannot be null");
+        }
         cellDescriptionGenerator = generator;
-        getState().hasDescriptions = (generator != null
-                || rowDescriptionGenerator != null);
+        getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null);
+        getState().cellTooltipContentMode = contentMode;
         datasourceExtension.refreshCache();
     }
 
@@ -6693,29 +6727,84 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
         return cellDescriptionGenerator;
     }
 
+    /**
+     * Gets the content mode used for cell descriptions.
+     * 
+     * @return the content mode used for cell descriptions, not
+     *         <code>null</code>
+     * @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
+     */
+    public ContentMode getCellDescriptionContentMode() {
+        return getState(false).cellTooltipContentMode;
+    }
+
     /**
      * Sets the {@code RowDescriptionGenerator} instance for generating optional
      * descriptions (tooltips) for Grid rows. If a
      * {@link CellDescriptionGenerator} is also set, the row description
      * generated by {@code generator} is used for cells for which the cell
-     * description generator returns null.
-     *
+     * description generator returns <code>null</code>.
+     * <p>
+     * The descriptions are rendered in the browser as HTML and the developer is
+     * responsible for ensuring no harmful HTML is used.
      *
      * @param generator
-     *            the description generator to use or {@code null} to remove a
-     *            previously set generator if any
+     *            the description generator to use or <code>null</code> to
+     *            remove a previously set generator if any
      *
+     * @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
      * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
      *
      * @since 7.6
      */
     public void setRowDescriptionGenerator(RowDescriptionGenerator generator) {
+        /*
+         * When porting this to the v7 version in Framework 8, the default
+         * should be changed to PREFORMATTED to preserve the more secure
+         * default that has accidentally been used there.
+         */
+        setRowDescriptionGenerator(generator, ContentMode.HTML);
+    }
+
+    /**
+     * Sets the {@code RowDescriptionGenerator} instance for generating optional
+     * descriptions (tooltips) for Grid rows. If a
+     * {@link CellDescriptionGenerator} is also set, the row description
+     * generated by {@code generator} is used for cells for which the cell
+     * description generator returns <code>null</code>.
+     *
+     * @param generator
+     *            the description generator to use or <code>null</code> to
+     *            remove a previously set generator if any
+     * @param contentMode
+     *            the content mode for row tooltips, not <code>null</code>
+     *
+     * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
+     *
+     * @since
+     */
+    public void setRowDescriptionGenerator(RowDescriptionGenerator generator,
+            ContentMode contentMode) {
+        if (contentMode == null) {
+            throw new IllegalArgumentException("Content mode cannot be null");
+        }
         rowDescriptionGenerator = generator;
         getState().hasDescriptions = (generator != null
                 || cellDescriptionGenerator != null);
+        getState().rowTooltipContentMode = contentMode;
         datasourceExtension.refreshCache();
     }
 
+    /**
+     * Gets the content mode used for row descriptions.
+     * 
+     * @return the content mode used for row descriptions, not <code>null</code>
+     * @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
+     */
+    public ContentMode getRowDescriptionContentMode() {
+        return getState(false).rowTooltipContentMode;
+    }
+
     /**
      * Returns the {@code RowDescriptionGenerator} instance used to generate
      * descriptions (tooltips) for Grid rows
index f1b6550912fd10ec2785fe210ef0efeaaca8a8b9..76d2bb4f18d20f29d5c68a4585829e8186eacc6e 100644 (file)
@@ -22,6 +22,7 @@ import java.util.List;
 import com.vaadin.shared.annotations.DelegateToWidget;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.TabIndexState;
+import com.vaadin.shared.ui.label.ContentMode;
 
 /**
  * The shared state for the {@link com.vaadin.ui.components.grid.Grid} component
@@ -209,4 +210,28 @@ public class GridState extends TabIndexState {
     @DelegateToWidget
     public boolean columnReorderingAllowed;
 
+    /**
+     * The content mode used for cell tooltips.
+     * 
+     * @since
+     */
+    /*
+     * When porting this to the v7 version in Framework 8, the default should be
+     * changed to PREFORMATTED to preserve the more secure default that has
+     * accidentally been used there.
+     */
+    public ContentMode cellTooltipContentMode = ContentMode.HTML;
+
+    /**
+     * The content mode used for row tooltips.
+     * 
+     * @since
+     */
+    /*
+     * When porting this to the v7 version in Framework 8, the default should be
+     * changed to PREFORMATTED to preserve the more secure default that has
+     * accidentally been used there.
+     */
+    public ContentMode rowTooltipContentMode = ContentMode.HTML;
+
 }
index ea8b5e5aa08e2307bd07ded46ce38304a2d68590..6923eeab71ed42b83eca4cc1799ff52c99616ca7 100644 (file)
@@ -129,6 +129,12 @@ body &.v-app .v-app-loading {
        cursor: default;
        background: #fff;
        box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .5);
+
+       pre.v-tooltip-pre {
+               font: inherit;
+               white-space: pre-wrap;
+               margin: 0;
+       }
 }
 .v-tooltip-text {
        overflow: auto;
index b8ab05351323c37b4e3ea9e322678822c38a3ab1..313661b1de7eabe79fa88d37cfe707c6f02c0850 100644 (file)
@@ -176,6 +176,12 @@ $v-tooltip-border-radius: $v-border-radius - 1px !default;
     h4 {
       color: inherit;
     }
+
+    pre.v-tooltip-pre {
+      font: inherit;
+      white-space: pre-wrap;
+      margin: 0;
+    }
   }
 }
 
index 6f1986720b3b69f15f1f62d2004972a1036c5aee..79efb678336fc35367137544bafe57dbdfba7e14 100644 (file)
@@ -47,6 +47,7 @@ import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.grid.ColumnResizeMode;
 import com.vaadin.shared.ui.grid.GridStaticCellType;
 import com.vaadin.shared.ui.grid.HeightMode;
+import com.vaadin.shared.ui.label.ContentMode;
 import com.vaadin.tests.components.AbstractComponentTest;
 import com.vaadin.ui.Button;
 import com.vaadin.ui.Button.ClickEvent;
@@ -139,7 +140,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
 
         @Override
         public String getDescription(RowReference row) {
-            return "Row tooltip for row " + row.getItemId();
+            return "<b>Row</b> tooltip\n for row " + row.getItemId();
         }
     };
 
@@ -148,7 +149,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
         @Override
         public String getDescription(CellReference cell) {
             if ("Column 0".equals(cell.getPropertyId())) {
-                return "Cell tooltip for row " + cell.getItemId()
+                return "<b>Cell</b> tooltip\n for row " + cell.getItemId()
                         + ", column 0";
             } else {
                 return null;
@@ -671,22 +672,41 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                     }
                 });
 
-        createBooleanAction("Row description generator", "State", false,
-                new Command<Grid, Boolean>() {
+        LinkedHashMap<String, ContentMode> contentModes = new LinkedHashMap<String, ContentMode>();
+        contentModes.put("None", null);
+        // Abusing an unused value for this special case
+        contentModes.put("Default", ContentMode.RAW);
+        contentModes.put("Plain text", ContentMode.TEXT);
+        contentModes.put("Preformatted", ContentMode.PREFORMATTED);
+        contentModes.put("HTML", ContentMode.HTML);
 
+        createSelectAction("Row description generator", "State", contentModes,
+                "None", new Command<Grid, ContentMode>() {
                     @Override
-                    public void execute(Grid c, Boolean value, Object data) {
-                        c.setRowDescriptionGenerator(
-                                value ? rowDescriptionGenerator : null);
+                    public void execute(Grid grid, ContentMode mode, Object data) {
+                        if (mode == null) {
+                            grid.setRowDescriptionGenerator(null);
+                        } else if (mode == ContentMode.RAW) {
+                            grid.setRowDescriptionGenerator(rowDescriptionGenerator);
+                        } else {
+                            grid.setRowDescriptionGenerator(
+                                    rowDescriptionGenerator, mode);
+                        }
                     }
                 });
 
-        createBooleanAction("Cell description generator", "State", false,
-                new Command<Grid, Boolean>() {
+        createSelectAction("Cell description generator", "State",
+                contentModes, "None", new Command<Grid, ContentMode>() {
                     @Override
-                    public void execute(Grid c, Boolean value, Object data) {
-                        c.setCellDescriptionGenerator(
-                                value ? cellDescriptionGenerator : null);
+                    public void execute(Grid grid, ContentMode mode, Object data) {
+                        if (mode == null) {
+                            grid.setCellDescriptionGenerator(null);
+                        } else if (mode == ContentMode.RAW) {
+                            grid.setCellDescriptionGenerator(cellDescriptionGenerator);
+                        } else {
+                            grid.setCellDescriptionGenerator(
+                                    cellDescriptionGenerator, mode);
+                        }
                     }
                 });
 
@@ -1157,10 +1177,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                     defaultRows, new Command<Grid, GridStaticCellType>() {
 
                         @Override
-                        public void execute(Grid grid, GridStaticCellType value,
-                                Object columnIndex) {
-                            final Object propertyId = getColumnProperty(
-                                    (Integer) columnIndex);
+                        public void execute(Grid grid,
+                                GridStaticCellType value, Object columnIndex) {
+                            final Object propertyId = getColumnProperty((Integer) columnIndex);
                             final HeaderCell cell = grid.getDefaultHeaderRow()
                                     .getCell(propertyId);
                             switch (value) {
index 068e6e7f60b3b3afa726de942386d20ba3762913..7a23b37b05133ffd4b2480ee5615082d6fd9f166 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.tests.components.grid.basicfeatures;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.util.List;
@@ -29,52 +30,130 @@ public class GridDescriptionGeneratorTest extends GridBasicFeaturesTest {
     @Test
     public void testCellDescription() {
         openTestURL();
-        selectMenuPath("Component", "State", "Cell description generator");
+        selectCellGenerator("Default");
 
-        getGridElement().getCell(1, 0).showTooltip();
-        String tooltipText = findElement(By.className("v-tooltip-text"))
-                .getText();
+        showCellTooltip(1, 0);
+        String tooltipText = getTooltipText();
         assertEquals("Tooltip text", "Cell tooltip for row 1, column 0",
                 tooltipText);
 
-        getGridElement().getCell(1, 1).showTooltip();
+        showCellTooltip(1, 1);
         assertTrue("Tooltip should not be present in cell (1, 1) ",
-                findElement(By.className("v-tooltip-text")).getText()
-                        .isEmpty());
+                getTooltipText().isEmpty());
     }
 
     @Test
     public void testRowDescription() {
         openTestURL();
-        selectMenuPath("Component", "State", "Row description generator");
+        selectRowGenerator("Default");
 
-        getGridElement().getCell(5, 3).showTooltip();
-        String tooltipText = findElement(By.className("v-tooltip-text"))
-                .getText();
+        showCellTooltip(5, 3);
+        String tooltipText = getTooltipText();
         assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);
 
-        getGridElement().getCell(15, 3).showTooltip();
-        tooltipText = findElement(By.className("v-tooltip-text")).getText();
+        showCellTooltip(15, 3);
+        tooltipText = getTooltipText();
         assertEquals("Tooltip text", "Row tooltip for row 15", tooltipText);
     }
 
     @Test
     public void testRowAndCellDescription() {
         openTestURL();
-        selectMenuPath("Component", "State", "Row description generator");
-        selectMenuPath("Component", "State", "Cell description generator");
+        selectRowGenerator("Default");
+        selectCellGenerator("Default");
 
-        getGridElement().getCell(5, 0).showTooltip();
-        String tooltipText = findElement(By.className("v-tooltip-text"))
-                .getText();
+        showCellTooltip(5, 0);
+        String tooltipText = getTooltipText();
         assertEquals("Tooltip text", "Cell tooltip for row 5, column 0",
                 tooltipText);
 
-        getGridElement().getCell(5, 3).showTooltip();
-        tooltipText = findElement(By.className("v-tooltip-text")).getText();
+        showCellTooltip(5, 3);
+        tooltipText = getTooltipText();
         assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);
     }
 
+    @Test
+    public void testContentTypes() {
+        openTestURL();
+
+        selectCellGenerator("Default");
+        showCellTooltip(1, 0);
+        /*
+         * When porting this to the v7 version in Framework 8, the default
+         * should be changed to PREFORMATTED to preserve the more secure default
+         * that has accidentally been used there.
+         */
+        assertHtmlTooltipShown();
+
+        selectRowGenerator("Default");
+        showCellTooltip(1, 1);
+        /*
+         * When porting this to the v7 version in Framework 8, the default
+         * should be changed to PREFORMATTED to preserve the more secure default
+         * that has accidentally been used there.
+         */
+        assertHtmlTooltipShown();
+
+        selectCellGenerator("Plain text");
+        showCellTooltip(2, 0);
+        assertPlainTooltipShown();
+
+        selectRowGenerator("Plain text");
+        showCellTooltip(2, 1);
+        assertPlainTooltipShown();
+
+        selectCellGenerator("Preformatted");
+        showCellTooltip(3, 0);
+        assertPreTooltipShown();
+
+        selectRowGenerator("Preformatted");
+        showCellTooltip(3, 1);
+        assertPreTooltipShown();
+
+        selectCellGenerator("HTML");
+        showCellTooltip(4, 0);
+        assertHtmlTooltipShown();
+
+        selectRowGenerator("HTML");
+        showCellTooltip(4, 1);
+        assertHtmlTooltipShown();
+    }
+
+    private void assertPreTooltipShown() {
+        assertTrue("Tooltip should contain <b> as text", getTooltipText()
+                .contains("<b>"));
+        assertTrue("Tooltip should contain a newline", getTooltipText()
+                .contains("\n"));
+    }
+
+    private void assertPlainTooltipShown() {
+        assertTrue("Tooltip should contain <b> as text", getTooltipText()
+                .contains("<b>"));
+        assertFalse("Tooltip should not contain a newline", getTooltipText()
+                .contains("\n"));
+    }
+
+    private void assertHtmlTooltipShown() {
+        assertTrue("Tooltip should contain <b> tag",
+                isElementPresent(By.cssSelector(".v-tooltip-text b")));
+    }
+
+    private void showCellTooltip(int row, int col) {
+        getGridElement().getCell(row, col).showTooltip();
+    }
+
+    private void selectCellGenerator(String name) {
+        selectMenuPath("Component", "State", "Cell description generator", name);
+    }
+
+    private void selectRowGenerator(String name) {
+        selectMenuPath("Component", "State", "Row description generator", name);
+    }
+
+    private String getTooltipText() {
+        return findElement(By.className("v-tooltip-text")).getText();
+    }
+
     @Override
     public List<DesiredCapabilities> getBrowsersToTest() {
         return getBrowsersExcludingFirefox();