From 65a4fd14bff1043ab62c045d7d176d2097242fb2 Mon Sep 17 00:00:00 2001 From: Anna Miroshnik Date: Tue, 25 Nov 2014 12:19:05 +0300 Subject: [PATCH] Position tooltips in the visible area (#15129). Based on Mika's reverted patch, with additional fix and test for regression "an empty tooltip appears while the application is initializing". Change-Id: I8237fc9340265708a05a7576a5d9e8e374dc1fea --- client/src/com/vaadin/client/VTooltip.java | 23 ++- .../tests/components/TooltipPosition.java | 70 +++++++ .../tests/components/TooltipPositionTest.java | 173 ++++++++++++++++++ .../components/menubar/MenuTooltipTest.java | 1 + 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/TooltipPosition.java create mode 100644 uitest/src/com/vaadin/tests/components/TooltipPositionTest.java diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java index edd1273bf5..47a1b71228 100644 --- a/client/src/com/vaadin/client/VTooltip.java +++ b/client/src/com/vaadin/client/VTooltip.java @@ -210,6 +210,14 @@ public class VTooltip extends VOverlay { x = Window.getClientWidth() - offsetWidth - MARGIN + Window.getScrollLeft(); } + + if (tooltipEventMouseX != EVENT_XY_POSITION_OUTSIDE) { + // Do not allow x to be zero, for otherwise the tooltip + // does not close when the mouse is moved (see + // isTooltipOpen()). #15129 + int minX = Window.getScrollLeft() + MARGIN; + x = Math.max(x, minX); + } return x; } @@ -245,6 +253,14 @@ public class VTooltip extends VOverlay { y = Window.getScrollTop(); } } + + if (tooltipEventMouseY != EVENT_XY_POSITION_OUTSIDE) { + // Do not allow y to be zero, for otherwise the tooltip + // does not close when the mouse is moved (see + // isTooltipOpen()). #15129 + int minY = Window.getScrollTop() + MARGIN; + y = Math.max(y, minY); + } return y; } }); @@ -323,6 +339,7 @@ public class VTooltip extends VOverlay { setPopupPosition(tooltipEventMouseX, tooltipEventMouseY); } + private int EVENT_XY_POSITION_OUTSIDE = -5000; private int tooltipEventMouseX; private int tooltipEventMouseY; @@ -332,11 +349,13 @@ public class VTooltip extends VOverlay { } private int getEventX(Event event, boolean isFocused) { - return isFocused ? -5000 : DOM.eventGetClientX(event); + return isFocused ? EVENT_XY_POSITION_OUTSIDE : DOM + .eventGetClientX(event); } private int getEventY(Event event, boolean isFocused) { - return isFocused ? -5000 : DOM.eventGetClientY(event); + return isFocused ? EVENT_XY_POSITION_OUTSIDE : DOM + .eventGetClientY(event); } @Override diff --git a/uitest/src/com/vaadin/tests/components/TooltipPosition.java b/uitest/src/com/vaadin/tests/components/TooltipPosition.java new file mode 100644 index 0000000000..30222722d9 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/TooltipPosition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +/** + * This UI is used for testing that a tooltip is not positioned partially + * outside the browser window when there is enough space to display it. + * + * @author Vaadin Ltd + */ +public class TooltipPosition extends AbstractTestUI { + + public static final int NUMBER_OF_BUTTONS = 5; + + @Override + protected void setup(VaadinRequest request) { + // These tooltip delay settings can be removed once #13854 is resolved. + getTooltipConfiguration().setOpenDelay(0); + getTooltipConfiguration().setQuickOpenDelay(0); + getTooltipConfiguration().setCloseTimeout(1000); + + VerticalLayout layout = new VerticalLayout(); + layout.setSpacing(true); + layout.setHeight(UI.getCurrent().getPage().getBrowserWindowHeight(), + Unit.PIXELS); + addComponent(layout); + for (int i = 0; i < NUMBER_OF_BUTTONS; i++) { + Button button = new Button("Button"); + button.setDescription(generateTooltipText()); + layout.addComponent(button); + } + } + + private String generateTooltipText() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < 50; i++) { + result.append("This is the line ").append(i) + .append(" of the long tooltip text.
"); + } + return result.toString(); + } + + @Override + public String getTestDescription() { + return "The tooltips of the buttons should not be clipped when there is enough space to display them."; + } + + @Override + public Integer getTicketNumber() { + return 15129; + } +} diff --git a/uitest/src/com/vaadin/tests/components/TooltipPositionTest.java b/uitest/src/com/vaadin/tests/components/TooltipPositionTest.java new file mode 100644 index 0000000000..4106374d64 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/TooltipPositionTest.java @@ -0,0 +1,173 @@ +/* + * 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; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriver.Window; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * Tests that the tooltip is positioned so that it fits in the displayed area. + * + * @author Vaadin Ltd + */ +public class TooltipPositionTest extends MultiBrowserTest { + + @Test + public void testRegression_EmptyTooltipShouldNotBeAppearedDuringInitialization() + throws Exception { + openTestURL(); + + waitForElementVisible(By.cssSelector(".v-tooltip")); + WebElement tooltip = driver.findElement(By.cssSelector(".v-tooltip")); + + Assert.assertTrue( + "This init tooltip with text ' ' is present in the DOM and should be entirely outside the browser window", + isOutsideOfWindow(tooltip)); + } + + @Test + public void testTooltipPosition() throws Exception { + openTestURL(); + for (int i = 0; i < TooltipPosition.NUMBER_OF_BUTTONS; i++) { + ButtonElement button = $(ButtonElement.class).get(i); + // Move the mouse to display the tooltip. + Actions actions = new Actions(driver); + actions.moveToElement(button, 10, 10); + actions.build().perform(); + waitUntil(tooltipToBeInsideWindow(By.cssSelector(".v-tooltip"), + driver.manage().window())); + + if (i < TooltipPosition.NUMBER_OF_BUTTONS - 1) { + // Remove the tooltip by moving the mouse. + actions = new Actions(driver); + actions.moveByOffset(300, 0); + actions.build().perform(); + waitUntil(tooltipNotToBeShown(By.cssSelector(".v-tooltip"), + driver.manage().window())); + } + } + } + + /* + * An expectation for checking that the tooltip found by the given locator + * is present in the DOM and entirely inside the browser window. The + * coordinate of the top left corner of the window is supposed to be (0, 0). + */ + private ExpectedCondition tooltipToBeInsideWindow( + final By tooltipLocator, final Window window) { + return new ExpectedCondition() { + + @Override + public Boolean apply(WebDriver input) { + List elements = findElements(tooltipLocator); + if (elements.isEmpty()) { + return false; + } + WebElement element = elements.get(0); + try { + if (!element.isDisplayed()) { + return false; + } + Point topLeft = element.getLocation(); + int xLeft = topLeft.getX(); + int yTop = topLeft.getY(); + if (xLeft < 0 || yTop < 0) { + return false; + } + Dimension elementSize = element.getSize(); + int xRight = xLeft + elementSize.getWidth() - 1; + int yBottom = yTop + elementSize.getHeight() - 1; + Dimension browserSize = window.getSize(); + return xRight < browserSize.getWidth() + && yBottom < browserSize.getHeight(); + } catch (StaleElementReferenceException e) { + return false; + } + } + + @Override + public String toString() { + return "the tooltip to be displayed inside the window"; + } + }; + }; + + /* + * An expectation for checking that the tooltip found by the given locator + * is not shown in the window, even partially. The top left corner of window + * should have coordinates (0, 0). + */ + private ExpectedCondition tooltipNotToBeShown( + final By tooltipLocator, final Window window) { + return new ExpectedCondition() { + + @Override + public Boolean apply(WebDriver input) { + List elements = findElements(tooltipLocator); + if (elements.isEmpty()) { + return true; + } + WebElement tooltip = elements.get(0); + try { + return isOutsideOfWindow(tooltip); + } catch (StaleElementReferenceException e) { + return true; + } + } + + @Override + public String toString() { + return "the tooltip not to be displayed inside the window"; + } + + }; + } + + private boolean isOutsideOfWindow(WebElement tooltip) { + if (!tooltip.isDisplayed()) { + return true; + } + // The tooltip is shown, at least partially, if + // its intervals of both horizontal and vertical coordinates + // overlap those of the window. + Point topLeft = tooltip.getLocation(); + Dimension tooltipSize = tooltip.getSize(); + Dimension windowSize = driver.manage().window().getSize(); + int xLeft = topLeft.getX(); + int yTop = topLeft.getY(); + int xRight = xLeft + tooltipSize.getWidth() - 1; + int yBottom = yTop + tooltipSize.getHeight() - 1; + boolean overlapHorizontally = !(xRight < 0 || xLeft >= windowSize + .getWidth()); + boolean overlapVertically = !(yBottom < 0 || yTop >= windowSize + .getHeight()); + return !(overlapHorizontally && overlapVertically); + } +} \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/menubar/MenuTooltipTest.java b/uitest/src/com/vaadin/tests/components/menubar/MenuTooltipTest.java index 24025b9f39..091f7be954 100644 --- a/uitest/src/com/vaadin/tests/components/menubar/MenuTooltipTest.java +++ b/uitest/src/com/vaadin/tests/components/menubar/MenuTooltipTest.java @@ -49,6 +49,7 @@ public class MenuTooltipTest extends MultiBrowserTest { Coordinates elementCoordinates = getCoordinates($(MenuBarElement.class) .first()); + sleep(1000); Mouse mouse = ((HasInputDevices) getDriver()).getMouse(); -- 2.39.5