2 * Copyright 2000-2016 Vaadin Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
16 package com.vaadin.tests.components.grid.basicfeatures.escalator;
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
28 import org.junit.Before;
29 import org.junit.ComparisonFailure;
30 import org.junit.Test;
31 import org.openqa.selenium.By;
32 import org.openqa.selenium.Keys;
33 import org.openqa.selenium.WebElement;
35 import com.vaadin.client.WidgetUtil;
36 import com.vaadin.shared.Range;
37 import com.vaadin.testbench.TestBenchElement;
38 import com.vaadin.testbench.elements.NotificationElement;
39 import com.vaadin.testbench.parallel.BrowserUtil;
40 import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest;
42 @SuppressWarnings("boxing")
43 public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest {
46 // separate strings made so that eclipse can show the concatenated string by hovering the mouse over the constant
48 // translate3d(0px, 40px, 123px);
49 // translate3d(24px, 15.251px, 0);
50 // translate(0, 40px);
51 private final static String TRANSLATE_VALUE_REGEX =
52 "translate(?:3d|)" // "translate" or "translate3d"
53 + "\\(" // literal "("
54 + "(" // start capturing the x argument
55 + "[0-9]+" // the integer part of the value
56 + "(?:" // start of the subpixel part of the value
57 + "\\.[0-9]" // if we have a period, there must be at least one number after it
58 + "[0-9]*" // any amount of accuracy afterwards is fine
59 + ")?" // the subpixel part is optional
61 + "(?:px)?" // we don't care if the values are suffixed by "px" or not.
63 + "(" // start capturing the y argument
64 + "[0-9]+" // the integer part of the value
65 + "(?:" // start of the subpixel part of the value
66 + "\\.[0-9]" // if we have a period, there must be at least one number after it
67 + "[0-9]*" // any amount of accuracy afterwards is fine
68 + ")?" // the subpixel part is optional
70 + "(?:px)?" // we don't care if the values are suffixed by "px" or not.
71 + "(?:, .*?)?" // the possible z argument, uninteresting (translate doesn't have one, translate3d does)
72 + "\\)" // literal ")"
73 + ";?"; // optional ending semicolon
77 private final static String PIXEL_VALUE_REGEX =
78 "(" // capture the pixel value
79 + "[0-9]+" // the pixel argument
80 + "(?:" // start of the subpixel part of the value
81 + "\\.[0-9]" // if we have a period, there must be at least one number after it
82 + "[0-9]*" // any amount of accuracy afterwards is fine
83 + ")?" // the subpixel part is optional
85 + "(?:px)?" // optional "px" string
86 + ";?"; // optional semicolon
89 // also matches "-webkit-transform";
90 private final static Pattern TRANSFORM_CSS_PATTERN = Pattern
91 .compile("transform: (.*?);");
92 private final static Pattern TOP_CSS_PATTERN = Pattern.compile(
93 "top: ([0-9]+(?:\\.[0-9]+)?(?:px)?);?", Pattern.CASE_INSENSITIVE);
94 private final static Pattern LEFT_CSS_PATTERN = Pattern.compile(
95 "left: ([0-9]+(?:\\.[0-9]+)?(?:px)?);?", Pattern.CASE_INSENSITIVE);
97 private final static Pattern TRANSLATE_VALUE_PATTERN = Pattern
98 .compile(TRANSLATE_VALUE_REGEX);
99 private final static Pattern PIXEL_VALUE_PATTERN = Pattern
100 .compile(PIXEL_VALUE_REGEX, Pattern.CASE_INSENSITIVE);
103 public void before() {
106 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, "Set 20px default height");
111 public void openVisibleSpacer() {
112 assertFalse("No spacers should be shown at the start",
113 spacersAreFoundInDom());
114 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
115 assertNotNull("Spacer should be shown after setting it", getSpacer(1));
119 public void closeVisibleSpacer() {
120 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
121 selectMenuPath(FEATURES, SPACERS, ROW_1, REMOVE);
122 assertNull("Spacer should not exist after removing it", getSpacer(1));
126 public void spacerPushesVisibleRowsDown() {
127 double oldTop = getElementTop(getBodyRow(2));
128 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
129 double newTop = getElementTop(getBodyRow(2));
131 assertGreater("Row below a spacer was not pushed down", newTop, oldTop);
135 public void addingRowAboveSpacerPushesItDown() {
136 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS);
137 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
138 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
140 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
141 double oldTop = getElementTop(getSpacer(1));
142 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
143 double newTop = getElementTop(getSpacer(2));
145 assertGreater("Spacer should've been pushed down (oldTop: " + oldTop
146 + ", newTop: " + newTop + ")", newTop, oldTop);
150 public void addingRowBelowSpacerDoesNotPushItDown() {
151 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS);
152 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
153 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
155 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
156 double oldTop = getElementTop(getSpacer(1));
157 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END);
158 double newTop = getElementTop(getSpacer(1));
160 assertEquals("Spacer should've not been pushed down", newTop, oldTop,
161 WidgetUtil.PIXEL_EPSILON);
165 public void addingRowBelowSpacerIsActuallyRenderedBelowWhenEscalatorIsEmpty() {
166 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS);
167 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
168 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING);
170 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
171 double spacerTop = getElementTop(getSpacer(1));
172 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END);
173 double rowTop = getElementTop(getBodyRow(2));
175 assertEquals("Next row should've been rendered below the spacer",
176 spacerTop + 100, rowTop, WidgetUtil.PIXEL_EPSILON);
180 public void addSpacerAtBottomThenScrollThere() {
181 selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX);
182 scrollVerticallyTo(999999);
184 assertFalse("Did not expect a notification",
185 $(NotificationElement.class).exists());
189 public void scrollToBottomThenAddSpacerThere() {
190 scrollVerticallyTo(999999);
191 long oldBottomScrollTop = getScrollTop();
192 selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX);
195 "Adding a spacer underneath the current viewport should "
196 + "not scroll anywhere",
197 oldBottomScrollTop, getScrollTop());
198 assertFalse("Got an unexpected notification",
199 $(NotificationElement.class).exists());
201 scrollVerticallyTo(999999);
203 assertFalse("Got an unexpected notification",
204 $(NotificationElement.class).exists());
205 assertGreater("Adding a spacer should've made the scrollbar scroll "
206 + "further", getScrollTop(), oldBottomScrollTop);
210 public void removingRowAboveSpacerMovesSpacerUp() {
211 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
212 WebElement spacer = getSpacer(1);
213 double originalElementTop = getElementTop(spacer);
215 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS,
216 REMOVE_ONE_ROW_FROM_BEGINNING);
217 assertLessThan("spacer should've moved up", getElementTop(spacer),
219 assertNull("No spacer for row 1 should be found after removing the "
220 + "top row", getSpacer(1));
224 public void removingSpacedRowRemovesSpacer() {
225 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
226 assertTrue("Spacer should've been found in the DOM",
227 spacersAreFoundInDom());
229 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS,
230 REMOVE_ONE_ROW_FROM_BEGINNING);
231 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS,
232 REMOVE_ONE_ROW_FROM_BEGINNING);
234 assertFalse("No spacers should be in the DOM after removing "
235 + "associated spacer", spacersAreFoundInDom());
240 public void spacersAreFixedInViewport_firstFreezeThenScroll() {
241 selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN);
242 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
244 "Spacer's left position should've been 0 at the " + "beginning",
245 0d, getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON);
248 scrollHorizontallyTo(scrollTo);
250 "Spacer's left position should've been " + scrollTo
251 + " after scrolling " + scrollTo + "px",
252 scrollTo, getElementLeft(getSpacer(1)),
253 WidgetUtil.PIXEL_EPSILON);
257 public void spacersAreFixedInViewport_firstScrollThenFreeze() {
258 selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN);
260 scrollHorizontallyTo(scrollTo);
261 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
263 "Spacer's left position should've been " + scrollTo
264 + " after scrolling " + scrollTo + "px",
265 scrollTo, getElementLeft(getSpacer(1)),
266 WidgetUtil.PIXEL_EPSILON);
270 public void addingMinusOneSpacerDoesNotScrollWhenScrolledAtTop() {
271 scrollVerticallyTo(5);
272 selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX);
274 "No scroll adjustment should've happened when adding the -1 spacer",
279 public void removingMinusOneSpacerScrolls() {
280 scrollVerticallyTo(5);
281 selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX);
282 selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, REMOVE);
283 assertEquals("Scroll adjustment should've happened when removing the "
284 + "-1 spacer", 0, getScrollTop());
288 public void scrollToRowWorksProperlyWithSpacers() throws Exception {
289 selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX);
290 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
293 * we check for row -2 instead of -1, because escalator has the one row
294 * buffered underneath the footer
296 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_75);
298 assertEquals("Row 75: 0,75", getBodyCell(-2, 0).getText());
300 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_25);
304 assertEquals("Row 25: 0,25", getBodyCell(0, 0).getText());
305 } catch (ComparisonFailure retryForIE10andIE11) {
307 * This seems to be some kind of subpixel/off-by-one-pixel error.
308 * Everything's scrolled correctly, but Escalator still loads one
309 * row above to the DOM, underneath the header. It's there, but it's
310 * not visible. We'll allow for that one pixel error.
312 assertEquals("Row 24: 0,24", getBodyCell(0, 0).getText());
317 public void scrollToSpacerFromAbove() throws Exception {
318 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
319 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
321 // Browsers might vary with a few pixels.
322 Range allowableScrollRange = Range.between(765, 780);
323 int scrollTop = (int) getScrollTop();
324 assertTrue("Scroll position was not " + allowableScrollRange + ", but "
325 + scrollTop, allowableScrollRange.contains(scrollTop));
329 public void scrollToSpacerFromBelow() throws Exception {
330 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
331 scrollVerticallyTo(999999);
332 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
334 // Browsers might vary with a few pixels.
335 Range allowableScrollRange = Range.between(1015, 1025);
336 int scrollTop = (int) getScrollTop();
337 assertTrue("Scroll position was not " + allowableScrollRange + ", but "
338 + scrollTop, allowableScrollRange.contains(scrollTop));
342 public void scrollToSpacerAlreadyInViewport() throws Exception {
343 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
344 scrollVerticallyTo(1000);
345 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
347 assertEquals(getScrollTop(), 1000);
351 public void scrollToRowAndSpacerFromAbove() throws Exception {
352 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
353 selectMenuPath(FEATURES, SPACERS, ROW_50,
354 SCROLL_HERE_SPACERBELOW_ANY_0PADDING);
356 // Browsers might vary with a few pixels.
357 Range allowableScrollRange = Range.between(765, 780);
358 int scrollTop = (int) getScrollTop();
359 assertTrue("Scroll position was not " + allowableScrollRange + ", but "
360 + scrollTop, allowableScrollRange.contains(scrollTop));
364 public void scrollToRowAndSpacerFromBelow() throws Exception {
365 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
366 scrollVerticallyTo(999999);
367 selectMenuPath(FEATURES, SPACERS, ROW_50,
368 SCROLL_HERE_SPACERBELOW_ANY_0PADDING);
370 // Browsers might vary with a few pixels.
371 Range allowableScrollRange = Range.between(995, 1005);
372 int scrollTop = (int) getScrollTop();
373 assertTrue("Scroll position was not " + allowableScrollRange + ", but "
374 + scrollTop, allowableScrollRange.contains(scrollTop));
378 public void scrollToRowAndSpacerAlreadyInViewport() throws Exception {
379 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
380 scrollVerticallyTo(950);
381 selectMenuPath(FEATURES, SPACERS, ROW_50,
382 SCROLL_HERE_SPACERBELOW_ANY_0PADDING);
384 assertEquals(getScrollTop(), 950);
388 public void domCanBeSortedWithFocusInSpacer() throws InterruptedException {
390 // Firefox behaves badly with focus-related tests - skip it.
391 if (BrowserUtil.isFirefox(super.getDesiredCapabilities())) {
395 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
396 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
398 WebElement inputElement = getEscalator()
399 .findElement(By.tagName("input"));
400 inputElement.click();
401 scrollVerticallyTo(30);
403 // Sleep needed because of all the JS we're doing, and to let
404 // the DOM reordering to take place.
407 assertFalse("Error message detected",
408 $(NotificationElement.class).exists());
412 public void spacersAreInsertedInCorrectDomPosition() {
413 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
415 WebElement tbody = getEscalator().findElement(By.tagName("tbody"));
416 WebElement spacer = getChild(tbody, 2);
417 String cssClass = spacer.getAttribute("class");
419 "element index 2 was not a spacer (class=\"" + cssClass + "\")",
420 cssClass.contains("-spacer"));
424 public void spacersAreInCorrectDomPositionAfterScroll() {
425 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
427 scrollVerticallyTo(32); // roughly one row's worth
429 WebElement tbody = getEscalator().findElement(By.tagName("tbody"));
430 WebElement spacer = getChild(tbody, 1);
431 String cssClass = spacer.getAttribute("class");
433 "element index 1 was not a spacer (class=\"" + cssClass + "\")",
434 cssClass.contains("-spacer"));
438 public void spacerScrolledIntoViewGetsFocus() {
439 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
440 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
441 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
443 tryToTabIntoFocusUpdaterElement();
444 assertEquals("input", getFocusedElement().getTagName());
448 public void spacerScrolledOutOfViewDoesNotGetFocus() {
449 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
450 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
451 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
453 tryToTabIntoFocusUpdaterElement();
454 assertNotEquals("input", getFocusedElement().getTagName());
458 public void spacerOpenedInViewGetsFocus() {
459 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
460 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
461 tryToTabIntoFocusUpdaterElement();
462 WebElement focusedElement = getFocusedElement();
463 assertEquals("input", focusedElement.getTagName());
467 public void spacerOpenedOutOfViewDoesNotGetFocus() {
468 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
469 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
471 tryToTabIntoFocusUpdaterElement();
472 assertNotEquals("input", getFocusedElement().getTagName());
476 public void spacerOpenedInViewAndScrolledOutAndBackAgainGetsFocus() {
477 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
478 selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX);
479 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_50);
480 selectMenuPath(FEATURES, SPACERS, ROW_1, SCROLL_HERE_ANY_0PADDING);
482 tryToTabIntoFocusUpdaterElement();
483 assertEquals("input", getFocusedElement().getTagName());
487 public void spacerOpenedOutOfViewAndScrolledInAndBackAgainDoesNotGetFocus() {
488 selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER);
489 selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX);
490 selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING);
491 selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_0);
493 tryToTabIntoFocusUpdaterElement();
494 assertNotEquals("input", getFocusedElement().getTagName());
497 private void tryToTabIntoFocusUpdaterElement() {
498 ((TestBenchElement) findElement(By.className("gwt-MenuBar"))).focus();
499 WebElement focusedElement = getFocusedElement();
500 focusedElement.sendKeys(Keys.TAB);
503 private WebElement getChild(WebElement parent, int childIndex) {
504 return (WebElement) executeScript(
505 "return arguments[0].children[" + childIndex + "];", parent);
508 private static double[] getElementDimensions(WebElement element) {
510 * we need to parse the style attribute, since using getCssValue gets a
511 * normalized value that is harder to parse.
513 String style = element.getAttribute("style");
515 String transform = getTransformFromStyle(style);
516 if (transform != null) {
517 return getTranslateValues(transform);
520 double[] result = new double[] { -1, -1 };
521 String left = getLeftFromStyle(style);
523 result[0] = getPixelValue(left);
525 String top = getTopFromStyle(style);
527 result[1] = getPixelValue(top);
530 if (result[0] != -1 && result[1] != -1) {
533 throw new IllegalArgumentException("Could not parse the position "
534 + "information from the CSS \"" + style + "\"");
538 private static double getElementTop(WebElement element) {
539 return getElementDimensions(element)[1];
542 private static double getElementLeft(WebElement element) {
543 return getElementDimensions(element)[0];
546 private static String getTransformFromStyle(String style) {
547 return getFromStyle(TRANSFORM_CSS_PATTERN, style);
550 private static String getTopFromStyle(String style) {
551 return getFromStyle(TOP_CSS_PATTERN, style);
554 private static String getLeftFromStyle(String style) {
555 return getFromStyle(LEFT_CSS_PATTERN, style);
558 private static String getFromStyle(Pattern pattern, String style) {
559 Matcher matcher = pattern.matcher(style);
560 if (matcher.find()) {
561 assertEquals("wrong amount of groups matched in " + style, 1,
562 matcher.groupCount());
563 return matcher.group(1);
570 * @return {@code [0] == x}, {@code [1] == y}
572 private static double[] getTranslateValues(String translate) {
573 Matcher matcher = TRANSLATE_VALUE_PATTERN.matcher(translate);
574 assertTrue("no matches for " + translate + " against "
575 + TRANSLATE_VALUE_PATTERN, matcher.find());
576 assertEquals("wrong amout of groups matched in " + translate, 2,
577 matcher.groupCount());
579 return new double[] { Double.parseDouble(matcher.group(1)),
580 Double.parseDouble(matcher.group(2)) };
583 private static double getPixelValue(String top) {
584 Matcher matcher = PIXEL_VALUE_PATTERN.matcher(top);
586 "no matches for \"" + top + "\" against " + PIXEL_VALUE_PATTERN,
588 assertEquals("wrong amount of groups matched in " + top, 1,
589 matcher.groupCount());
590 return Double.parseDouble(matcher.group(1));