Browse Source

TabSheet focus/blur events fixed (#14304)

The blur and focus events should be linked with the TabSheet
component as a whole. Any click inside the TabSheet should
trigger one single focus and any leave the blur.

Change-Id: Id24a2fab12aafe6f7aa3a44635e5b9e935a1cfe1
tags/7.3.0.rc1
Bogdan Udrescu 9 years ago
parent
commit
8c4ddbb283

+ 16
- 0
WebContent/html-tests/ComponentFocus.html View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script type="text/javascript">

</script>

</head>
<body>
<button onfocus="console.log('button focus');" onblur="console.log('button blur');">Focus me</button>
<textarea onfocus="console.log('textarea focus');" onblur="console.log('textarea blur');">Focus me too</textarea>
</body>
</html>

+ 538
- 156
client/src/com/vaadin/client/ui/VTabsheet.java View File

@@ -1,12 +1,12 @@
/*
* 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
@@ -42,9 +42,12 @@ import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasBlurHandlers;
import com.google.gwt.event.dom.client.HasFocusHandlers;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.HasMouseDownHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
@@ -74,7 +77,9 @@ import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
import com.vaadin.shared.ui.tabsheet.TabsheetState;

public class VTabsheet extends VTabsheetBase implements Focusable,
FocusHandler, BlurHandler, KeyDownHandler, SubPartAware {
SubPartAware,
// TODO: These listeners are due to be removed in 7.3
FocusHandler, BlurHandler, KeyDownHandler {

private static class VCloseEvent {
private Tab tab;
@@ -95,10 +100,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Representation of a single "tab" shown in the TabBar
*
*
*/
public static class Tab extends SimplePanel implements HasFocusHandlers,
HasBlurHandlers, HasKeyDownHandlers {
HasBlurHandlers, HasMouseDownHandlers, HasKeyDownHandlers {
private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
+ "-first";
@@ -152,10 +157,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

Roles.getTabRole().setAriaLabelledbyProperty(getElement(),
Id.of(tabCaption.getElement()));

addFocusHandler(getTabsheet());
addBlurHandler(getTabsheet());
addKeyDownHandler(getTabsheet());
}

public boolean isHiddenOnServer() {
@@ -197,7 +198,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Toggles the style names for the Tab
*
*
* @param selected
* true if the Tab is selected
* @param first
@@ -281,6 +282,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return addDomHandler(handler, BlurEvent.getType());
}

@Override
public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
return addDomHandler(handler, MouseDownEvent.getType());
}

@Override
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return addDomHandler(handler, KeyDownEvent.getType());
@@ -420,8 +426,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

}

static class TabBar extends ComplexPanel implements ClickHandler,
VCloseHandler {
static class TabBar extends ComplexPanel implements VCloseHandler {

private final Element tr = DOM.createTR();

@@ -443,6 +448,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
setStyleName(spacerTd, CLASSNAME + "-spacertd");
DOM.appendChild(tr, spacerTd);
DOM.appendChild(spacerTd, DOM.createDiv());

setElement(el);
}

@@ -460,10 +466,20 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return DOM.asOld(tr);
}

/**
* Gets the number of tabs from the tab bar.
*
* @return the number of tabs from the tab bar.
*/
public int getTabCount() {
return getWidgetCount();
}

/**
* Adds a tab to the tab bar.
*
* @return the added tab.
*/
public Tab addTab() {
Tab t = new Tab(this);
int tabIndex = getTabCount();
@@ -476,31 +492,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
t.setStyleNames(false, true);
}

t.addClickHandler(this);
getTabsheet().selectionHandler.registerTab(t);

t.setCloseHandler(this);

return t;
}

@Override
public void onClick(ClickEvent event) {
TabCaption caption = (TabCaption) event.getSource();
Element targetElement = event.getNativeEvent().getEventTarget()
.cast();
// the tab should not be focused if the close button was clicked
if (targetElement == caption.getCloseButton()) {
return;
}

int index = getWidgetIndex(caption.getParent());

navigateTab(getTabsheet().focusedTabIndex, index);
getTabsheet().focusedTabIndex = index;
getTabsheet().focusedTab = getTab(index);
getTabsheet().focus();
getTabsheet().loadTabSheet(index);
}

/**
* Gets the tab sheet instance where the tab bar is attached to.
*
* @return the tab sheet instance where the tab bar is attached to.
*/
public VTabsheet getTabsheet() {
return tabsheet;
}
@@ -538,7 +541,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
}

public void navigateTab(int fromIndex, int toIndex) {
public Tab navigateTab(int fromIndex, int toIndex) {
Tab newNavigated = getTab(toIndex);
if (newNavigated == null) {
throw new IllegalArgumentException(
@@ -553,6 +556,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
oldNavigated.setStyleNames(oldNavigated.equals(selected),
isFirstVisibleTab(fromIndex), false);
}

return newNavigated;
}

public void removeTab(int i) {
@@ -580,7 +585,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Returns the index of the first visible tab
*
*
* @return
*/
private int getFirstVisibleTab() {
@@ -589,7 +594,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Find the next visible tab. Returns -1 if none is found.
*
*
* @param i
* @return
*/
@@ -608,7 +613,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Find the previous visible tab. Returns -1 if none is found.
*
*
* @param i
* @return
*/
@@ -663,7 +668,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/** For internal use only. May be removed or replaced in the future. */
// tabbar and 'scroller' container
public final Element tabs;
Tab focusedTab;
/**
* The tabindex property (position in the browser's focus cycle.) Named like
* this to avoid confusion with activeTabIndex.
@@ -697,9 +702,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

private String currentStyle;

/** For internal use only. May be removed or replaced in the future. */
private int focusedTabIndex = 0;

/**
* @return Whether the tab could be selected or not.
*/
@@ -720,11 +722,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Load the content of a tab of the provided index.
*
*
* @param index
* of the tab to load
*
* @return true if the specified sheet gets loaded, otherwise false.
*/
public void loadTabSheet(int tabIndex) {
public boolean loadTabSheet(int tabIndex) {
if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) {
tb.selectTab(tabIndex);

@@ -741,12 +745,16 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
waitingForResponse = true;

tb.getTab(tabIndex).focus(); // move keyboard focus to active tab

return true;
}

return false;
}

/**
* Returns the currently displayed widget in the tab panel.
*
*
* @since 7.2
* @return currently displayed content widget
*/
@@ -756,7 +764,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Returns the client to server RPC proxy for the tabsheet.
*
*
* @since 7.2
* @return RPC proxy
*/
@@ -766,10 +774,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* For internal use only.
*
*
* Avoid using this method directly and use appropriate superclass methods
* where applicable.
*
*
* @deprecated since 7.2 - use more specific methods instead (getRpcProxy(),
* getConnectorForWidget(Widget) etc.)
* @return ApplicationConnection
@@ -799,9 +807,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public VTabsheet() {
super(CLASSNAME);

addHandler(this, FocusEvent.getType());
addHandler(this, BlurEvent.getType());

// Tab scrolling
getElement().getStyle().setOverflow(Overflow.HIDDEN);
tabs = DOM.createDiv();
@@ -812,18 +817,21 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
Roles.getTablistRole().setAriaHiddenState(scroller, true);

DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);

scrollerPrev = DOM.createButton();
scrollerPrev.setTabIndex(-1);
DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ "Prev");
Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true);
DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
DOM.sinkEvents(scrollerPrev, Event.ONCLICK | Event.ONMOUSEDOWN);

scrollerNext = DOM.createButton();
scrollerNext.setTabIndex(-1);
DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+ "Next");
Roles.getTablistRole().setAriaHiddenState(scrollerNext, true);
DOM.sinkEvents(scrollerNext, Event.ONCLICK);
DOM.sinkEvents(scrollerNext, Event.ONCLICK | Event.ONMOUSEDOWN);

DOM.appendChild(getElement(), tabs);

// Tabs
@@ -856,35 +864,68 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

@Override
public void onBrowserEvent(Event event) {
com.google.gwt.dom.client.Element eventTarget = DOM
.eventGetTarget(event);

if (event.getTypeInt() == Event.ONCLICK) {

// Tab scrolling
if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
int newFirstIndex = tb.scrollLeft(scrollerIndex);
if (newFirstIndex != -1) {
scrollerIndex = newFirstIndex;
updateTabScroller();
}
event.stopPropagation();
return;
} else if (isClippedTabs()
&& DOM.eventGetTarget(event) == scrollerNext) {
int newFirstIndex = tb.scrollRight(scrollerIndex);
if (eventTarget == scrollerPrev || eventTarget == scrollerNext) {
scrollAccordingToScrollTarget(eventTarget);

if (newFirstIndex != -1) {
scrollerIndex = newFirstIndex;
updateTabScroller();
}
event.stopPropagation();
}

} else if (event.getTypeInt() == Event.ONMOUSEDOWN) {

if (eventTarget == scrollerPrev || eventTarget == scrollerNext) {
// In case the focus was previously on a Tab, we need to cancel
// the upcoming blur on the Tab which will follow this mouse
// down event.
focusBlurManager.cancelNextBlurSchedule();

return;
}
}

super.onBrowserEvent(event);
}

/*
* Scroll the tab bar according to the last scrollTarget (the scroll button
* pressed).
*/
private void scrollAccordingToScrollTarget(
com.google.gwt.dom.client.Element scrollTarget) {
if (scrollTarget == null) {
return;
}

int newFirstIndex = -1;

// Scroll left.
if (isScrolledTabs() && scrollTarget == scrollerPrev) {
newFirstIndex = tb.scrollLeft(scrollerIndex);

// Scroll right.
} else if (isClippedTabs() && scrollTarget == scrollerNext) {
newFirstIndex = tb.scrollRight(scrollerIndex);
}

if (newFirstIndex != -1) {
scrollerIndex = newFirstIndex;
updateTabScroller();
}

// For this to work well, make sure the method gets called only from
// user events.
selectionHandler.focusTabAtIndex(scrollerIndex);
}

/**
* Checks if the tab with the selected index has been scrolled out of the
* view (on the left side).
*
*
* @param index
* @return
*/
@@ -1016,7 +1057,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Renders the widget content for a tab sheet.
*
*
* @param newWidget
*/
public void renderContent(Widget newWidget) {
@@ -1239,69 +1280,444 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}

@Override
public void onBlur(BlurEvent event) {
getVTooltip().hideTooltip();
public void focus() {
getActiveTab().focus();
}

if (focusedTab != null && focusedTab == event.getSource()) {
focusedTab.removeAssistiveDescription();
focusedTab = null;
if (connector.hasEventListener(EventId.BLUR)) {
connector.getRpcProxy(FocusAndBlurServerRpc.class).blur();
}
}
public void blur() {
getActiveTab().blur();
}

@Override
public void onFocus(FocusEvent event) {
if (focusedTab == null && event.getSource() instanceof Tab) {
focusedTab = (Tab) event.getSource();
/*
* Gets the active tab.
*/
private Tab getActiveTab() {
return tb.getTab(activeTabIndex);
}

/*
* The focus and blur manager instance.
*/
private FocusBlurManager focusBlurManager = new FocusBlurManager();

/*
* Manage the TabSheet component blur event.
*/
private class FocusBlurManager implements FocusedTabProvider {

// The real tab with focus on it. If the focus goes to another element
// in the page this will be null.
private Tab focusedTab;

@Override
public Tab getFocusedTab() {
return focusedTab;
}

@Override
public void setFocusedTab(Tab focusedTab) {
this.focusedTab = focusedTab;
}

/**
* Process the focus event no matter where it occurs on the tab bar.
* We've added this method
*
* @since
* @param newFocusTab
* the new focused tab.
* @see #blur(Tab)
*/
public void focus(Tab newFocusTab) {

if (connector.hasEventListener(EventId.FOCUS)) {
connector.getRpcProxy(FocusAndBlurServerRpc.class).focus();

// Send the focus event only first time when we focus on any
// tab. The focused tab will be reseted on the last blur.
if (focusedTab == null) {
connector.getRpcProxy(FocusAndBlurServerRpc.class).focus();
}
}

removeBlurSchedule();

setFocusedTab(newFocusTab);

if (focusedTab.hasTooltip()) {
focusedTab.setAssistiveDescription(getVTooltip().getUniqueId());
getVTooltip().showAssistive(focusedTab.getTooltipInfo());
}

}

/**
* Process the blur event for the current focusedTab.
*
* @param blurSource
* the source of the blur.
*
* @see #focus(Tab)
*/
public void blur(Tab blurSource) {
if (focusedTab != null && focusedTab == blurSource) {

if (connector.hasEventListener(EventId.BLUR)) {
scheduleBlur(focusedTab);
}
}
}

/*
* The last blur command to be executed.
*/
private BlurCommand blurCommand;

/**
* Schedule a new blur event for a deferred execution.
*/
public void scheduleBlur(Tab blurSource) {

if (cancelNextBlurSchedule) {

// This will set the stopNextBlurCommand back to false as well.
removeBlurSchedule();

// Leave this though to make things clear for the developer.
cancelNextBlurSchedule = false;
return;
}

removeBlurSchedule();

blurCommand = new BlurCommand(blurSource, this);
blurCommand.scheduleDeferred();
}

/**
* Remove the last blur command from execution.
*/
public void removeBlurSchedule() {
if (blurCommand != null) {
blurCommand.stopSchedule();
blurCommand = null;
}

// Maybe this is not the best place to reset this, but we really
// want to make sure it'll reset at any time when something
// interact with the blur manager. Might change it later.
cancelNextBlurSchedule = false;
}

/**
* Cancel the next possible scheduled execution.
*/
public void cancelNextBlurSchedule() {

// Make sure there's still no other command to be executed.
removeBlurSchedule();

cancelNextBlurSchedule = true;
}

// Stupid workaround...
private boolean cancelNextBlurSchedule = false;

}

/*
* Wraps the focused tab.
*/
private interface FocusedTabProvider {

/**
* Gets the focused Tab.
*
* @return the focused Tab.
*/
Tab getFocusedTab();

/**
* Sets the focused Tab.
*
* @param focusedTab
* the focused Tab.
*/
void setFocusedTab(Tab focusedTab);

}

/*
* Execute the final blur command.
*/
private class BlurCommand implements Command {

/*
* The blur source.
*/
private Tab blurSource;

/*
* Provide the current focused Tab.
*/
private FocusedTabProvider focusedTabProvider;

/**
* Create the blur command using the blur source.
*
* @param blurSource
* the source.
* @param focusedTabProvider
* provides the current focused tab.
*/
public BlurCommand(Tab blurSource, FocusedTabProvider focusedTabProvider) {
this.blurSource = blurSource;
this.focusedTabProvider = focusedTabProvider;
}

/**
* Stop the command from being executed.
*
* @since
*/
public void stopSchedule() {
blurSource = null;
}

/**
* Schedule the command for a deferred execution.
*
* @since
*/
public void scheduleDeferred() {
Scheduler.get().scheduleDeferred(this);
}

@Override
public void execute() {

Tab focusedTab = focusedTabProvider.getFocusedTab();

if (blurSource == null) {
return;
}

// The focus didn't change since this blur triggered, so
// the new focused element is not a tab.
if (focusedTab == blurSource) {

// We're certain there's no focus anymore.
focusedTab.removeAssistiveDescription();
focusedTabProvider.setFocusedTab(null);

connector.getRpcProxy(FocusAndBlurServerRpc.class).blur();
}

// Call this to set it to null and be consistent.
focusBlurManager.removeBlurSchedule();
}
}

@Override
public void focus() {
tb.getTab(activeTabIndex).focus();
public void onBlur(BlurEvent event) {
selectionHandler.onBlur(event);
}

public void blur() {
tb.getTab(activeTabIndex).blur();
@Override
public void onFocus(FocusEvent event) {
selectionHandler.onFocus(event);
}

@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getSource() instanceof Tab) {
int keycode = event.getNativeEvent().getKeyCode();

// Scroll throw the tabs.
if (!event.isAnyModifierKeyDown()) {
if (keycode == getPreviousTabKey()) {
selectPreviousTab();
event.stopPropagation();
} else if (keycode == getNextTabKey()) {
selectNextTab();
event.stopPropagation();
} else if (keycode == getCloseTabKey()) {
Tab tab = tb.getTab(activeTabIndex);
if (tab.isClosable()) {
tab.onClose();
}
} else if (keycode == getSelectTabKey()) {
loadTabSheet(focusedTabIndex);
selectionHandler.onKeyDown(event);
}

/*
* The tabs selection handler instance.
*/
private final TabSelectionHandler selectionHandler = new TabSelectionHandler();

/*
* Handle the events for selecting the tabs.
*/
private class TabSelectionHandler implements FocusHandler, BlurHandler,
KeyDownHandler, ClickHandler, MouseDownHandler {

/** For internal use only. May be removed or replaced in the future. */
// The current visible focused index.
private int focusedTabIndex = 0;

/**
* Register the tab to the selection handler.
*
* @param tab
* the tab to register.
*/
public void registerTab(Tab tab) {

// TODO: change VTabsheet.this to this in 7.3
tab.addBlurHandler(VTabsheet.this);
tab.addFocusHandler(VTabsheet.this);
tab.addKeyDownHandler(VTabsheet.this);

tab.addClickHandler(this);
tab.addMouseDownHandler(this);
}

@Override
public void onBlur(final BlurEvent event) {

getVTooltip().hideTooltip();

Object blurSource = event.getSource();

if (blurSource instanceof Tab) {
focusBlurManager.blur((Tab) blurSource);
}
}

@Override
public void onFocus(FocusEvent event) {

if (event.getSource() instanceof Tab) {
focusBlurManager.focus((Tab) event.getSource());
}
}

@Override
public void onClick(ClickEvent event) {

// IE doesn't trigger focus when click, so we need to make sure
// the previous blur deferred command will get killed.
focusBlurManager.removeBlurSchedule();

TabCaption caption = (TabCaption) event.getSource();
Element targetElement = event.getNativeEvent().getEventTarget()
.cast();
// the tab should not be focused if the close button was clicked
if (targetElement == caption.getCloseButton()) {
return;
}

int index = tb.getWidgetIndex(caption.getParent());

tb.navigateTab(focusedTabIndex, index);

// Prevent the page from scrolling when hitting space
// (select key) to select the current tab.
event.preventDefault();
focusedTabIndex = index;

if (!loadTabSheet(index)) {

// This needs to be called at the end, as the activeTabIndex
// is set in the loadTabSheet.
focus();
}
}

@Override
public void onMouseDown(MouseDownEvent event) {

if (event.getSource() instanceof Tab) {

// IE doesn't trigger focus when click, so we need to make sure
// the
// next blur deferred command will get killed.
focusBlurManager.cancelNextBlurSchedule();
}
}

@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getSource() instanceof Tab) {
int keycode = event.getNativeEvent().getKeyCode();

if (!event.isAnyModifierKeyDown()) {
if (keycode == getPreviousTabKey()) {
selectPreviousTab();
event.stopPropagation();

} else if (keycode == getNextTabKey()) {
selectNextTab();
event.stopPropagation();

} else if (keycode == getCloseTabKey()) {
Tab tab = tb.getTab(activeTabIndex);
if (tab.isClosable()) {
tab.onClose();
}

} else if (keycode == getSelectTabKey()) {
loadTabSheet(focusedTabIndex);

// Prevent the page from scrolling when hitting space
// (select key) to select the current tab.
event.preventDefault();
}
}
}
}

/*
* Left arrow key selection.
*/
private void selectPreviousTab() {
int newTabIndex = focusedTabIndex;
// Find the previous visible and enabled tab if any.
do {
newTabIndex--;
} while (newTabIndex >= 0 && !canSelectTab(newTabIndex));

if (newTabIndex >= 0) {
keySelectTab(newTabIndex);
}
}

/*
* Right arrow key selection.
*/
private void selectNextTab() {
int newTabIndex = focusedTabIndex;
// Find the next visible and enabled tab if any.
do {
newTabIndex++;
} while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));

if (newTabIndex < getTabCount()) {
keySelectTab(newTabIndex);
}
}

/*
* Select the specified tab using left/right key.
*/
private void keySelectTab(int newTabIndex) {
Tab tab = tb.getTab(newTabIndex);
if (tab == null) {
return;
}

// Focus the tab, otherwise the selected one will loose focus and
// TabSheet will get blurred.
focusTabAtIndex(newTabIndex);

tb.navigateTab(focusedTabIndex, newTabIndex);

focusedTabIndex = newTabIndex;
}

/**
* Focus the specified tab. Make sure to call this only from user
* events, otherwise will break things.
*
* @param tabIndex
* the index of the tab to set.
*/
void focusTabAtIndex(int tabIndex) {
Tab tabToFocus = tb.getTab(tabIndex);
if (tabToFocus != null) {
tabToFocus.focus();
}
}

}

/**
@@ -1341,66 +1757,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return KeyCodes.KEY_DELETE;
}

private void selectPreviousTab() {
int newTabIndex = focusedTabIndex;
// Find the previous visible and enabled tab if any.
do {
newTabIndex--;
} while (newTabIndex >= 0 && !canSelectTab(newTabIndex));

if (newTabIndex >= 0) {
tb.navigateTab(focusedTabIndex, newTabIndex);
focusedTabIndex = newTabIndex;

// If this TabSheet already has focus, set the new selected tab
// as focused.
if (focusedTab != null) {
focusedTab = tb.getTab(focusedTabIndex);
focusedTab.focus();
}
}
}

private void selectNextTab() {
int newTabIndex = focusedTabIndex;
// Find the next visible and enabled tab if any.
do {
newTabIndex++;
} while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));

if (newTabIndex < getTabCount()) {
private void scrollIntoView(Tab tab) {

tb.navigateTab(focusedTabIndex, newTabIndex);
focusedTabIndex = newTabIndex;
if (!tab.isHiddenOnServer()) {

// If this TabSheet already has focus, set the new selected tab
// as focused.
if (focusedTab != null) {
focusedTab = tb.getTab(focusedTabIndex);
focusedTab.focus();
}
}
}
// Check for visibility first as clipped tabs to the right are
// always visible.
// On IE8 a tab with false visibility would have the bounds of the
// full TabBar.
if (!tab.isVisible()) {
while (!tab.isVisible()) {
scrollerIndex = tb.scrollLeft(scrollerIndex);
}
updateTabScroller();

private void scrollIntoView(Tab tab) {
if (!tab.isHiddenOnServer()) {
if (isClipped(tab)) {
} else if (isClipped(tab)) {
while (isClipped(tab) && scrollerIndex != -1) {
scrollerIndex = tb.scrollRight(scrollerIndex);
}
updateTabScroller();
} else if (!tab.isVisible()) {
while (!tab.isVisible()) {
scrollerIndex = tb.scrollLeft(scrollerIndex);
}
updateTabScroller();
}
}
}

/**
* Makes tab bar visible.
*
*
* @since 7.2
*/
public void showTabs() {
@@ -1411,7 +1793,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,

/**
* Makes tab bar invisible.
*
*
* @since 7.2
*/
public void hideTabs() {

+ 0
- 241
uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html View File

@@ -1,241 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>TabKeyboardNavigation</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">TabKeyboardNavigation</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>/run/TabKeyboardNavigation?restartApplication</td>
<td></td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
<td>9,8</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
<td>right</td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 1</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
<td>space</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 2</td>
</tr>
<tr>
<td>screenCapture</td>
<td></td>
<td>tab2</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
<td>right</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
<td>right</td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 2</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
<td>space</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 5</td>
</tr>
<tr>
<td>screenCapture</td>
<td></td>
<td>skip-disabled-to-tab5</td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
<td>right</td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 5</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
<td>space</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 6</td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
<td></td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[8]/domChild[0]/domChild[0]/domChild[0]</td>
<td>18,10</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[8]</td>
<td>right</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[9]</td>
<td>right</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10]</td>
<td>right</td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 9</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10]</td>
<td>space</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 12</td>
</tr>
<tr>
<td>screenCapture</td>
<td></td>
<td>scrolled-right-to-tab-12</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[4]/domChild[0]/domChild[0]/domChild[0]</td>
<td>11,2</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[4]</td>
<td>left</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
<td>left</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
<td>left</td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td>1000</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 5</td>
</tr>
<tr>
<td>pressSpecialKey</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
<td>space</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>Tab 1</td>
</tr>
<tr>
<td>screenCapture</td>
<td></td>
<td>scrolled-left-to-tab-1</td>
</tr>

</tbody></table>
</body>
</html>

+ 25
- 7
uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java View File

@@ -6,7 +6,8 @@ import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.tests.components.TestBase;
import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.tests.util.Log;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
@@ -19,7 +20,16 @@ import com.vaadin.ui.TabSheet.Tab;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;

public class TabKeyboardNavigation extends TestBase {
/**
* Test if the click and key tab selection in a tabsheet generate the correct
* focus/blur events.
*
* The solution was broken in ticket (#14304)
*
* @since
* @author Vaadin Ltd
*/
public class TabKeyboardNavigation extends AbstractTestUI {

int index = 1;
ArrayList<Component> tabs = new ArrayList<Component>();
@@ -27,18 +37,18 @@ public class TabKeyboardNavigation extends TestBase {
Log focusblur = new Log(10);

@Override
protected void setup() {
protected void setup(VaadinRequest request) {
ts.setWidth("500px");
ts.setHeight("500px");

ts.addListener(new FocusListener() {
ts.addFocusListener(new FocusListener() {
@Override
public void focus(FocusEvent event) {
focusblur.log("Tabsheet focused!");
}
});

ts.addListener(new BlurListener() {
ts.addBlurListener(new BlurListener() {
@Override
public void blur(BlurEvent event) {
focusblur.log("Tabsheet blurred!");
@@ -74,7 +84,7 @@ public class TabKeyboardNavigation extends TestBase {
}

@Override
protected String getDescription() {
protected String getTestDescription() {
return "The tab bar should be focusable and arrow keys should switch tabs. The del key should close a tab if closable.";
}

@@ -83,10 +93,18 @@ public class TabKeyboardNavigation extends TestBase {
return 5100;
}

public final static String LABEL_ID = "sheetLabel";

public final static String labelID(int index) {
return LABEL_ID + index;
}

private Tab addTab() {
Layout content = new VerticalLayout();
tabs.add(content);
content.addComponent(new Label("Tab " + index));
Label label = new Label("Tab " + index);
label.setId(labelID(index));
content.addComponent(label);
content.addComponent(new TextField());
Tab tab = ts.addTab(content, "Tab " + index, null);
if (index == 2) {

+ 188
- 0
uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java View File

@@ -0,0 +1,188 @@
/*
* 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.tabsheet;

import java.io.IOException;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;

import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.tb3.MultiBrowserTest;

/**
* Add TB3 test as the TB2 one failed on keyboard events.
*
* @since
* @author Vaadin Ltd
*/
public class TabKeyboardNavigationTest extends MultiBrowserTest {

@Test
public void testFocus() throws InterruptedException, IOException {
openTestURL();

click(1);
sendKeys(1, Keys.ARROW_RIGHT);

assertSheet(1);
sendKeys(2, Keys.SPACE);
assertSheet(2);
compareScreen("tab2");

sendKeys(2, Keys.ARROW_RIGHT);
sendKeys(3, Keys.ARROW_RIGHT);
assertSheet(2);

sendKeys(5, Keys.SPACE);
assertSheet(5);
compareScreen("skip-disabled-to-tab5");

TestBenchElement addTabButton = (TestBenchElement) getDriver()
.findElements(By.className("v-button")).get(0);

click(addTabButton);

click(5);
sendKeys(5, Keys.ARROW_RIGHT);
assertSheet(5);

sendKeys(6, Keys.SPACE);
assertSheet(6);

click(addTabButton);
click(addTabButton);
click(addTabButton);
click(addTabButton);
click(addTabButton);
click(addTabButton);

click(8);
compareScreen("click-tab-8");

sendKeys(8, Keys.ARROW_RIGHT);
sendKeys(9, Keys.SPACE);
click(9);
compareScreen("tab-9");

sendKeys(9, Keys.ARROW_RIGHT);
Thread.sleep(DELAY);

sendKeys(10, Keys.ARROW_RIGHT);

// Here PhantomJS used to fail. Or when accessing tab2. The fix was to
// call the elem.click(x, y) using the (x, y) position instead of the
// elem.click() without any arguments.
sendKeys(11, Keys.ARROW_RIGHT);

assertSheet(9);
sendKeys(12, Keys.SPACE);
assertSheet(12);
compareScreen("scrolled-right-to-tab-12");

click(5);

sendKeys(5, Keys.ARROW_LEFT);

// Here IE8 used to fail. A hidden <div> in IE8 would have the bounds of
// it's parent, and when trying to see in which direction to scroll
// (left or right) to make the key selected tab visible, the
// VTabSheet.scrollIntoView(Tab) used to check first whether the tab
// isClipped. On IE8 this will always return true for both hidden tabs
// on the left and clipped tabs on the right. So instead of going to
// left, it'll search all the way to the right.
sendKeys(3, Keys.ARROW_LEFT);
sendKeys(2, Keys.ARROW_LEFT);
assertSheet(5);

sendKeys(1, Keys.SPACE);
assertSheet(1);
compareScreen("scrolled-left-to-tab-1");
}

/*
* Press key on the element.
*/
private void sendKeys(int tabIndex, Keys key) throws InterruptedException {
sendKeys(tab(tabIndex), key);
}

/*
* Press key on the element.
*/
private void sendKeys(TestBenchElement element, Keys key)
throws InterruptedException {

element.sendKeys(key);
if (DELAY > 0) {
sleep(DELAY);
}
}

/*
* Click on the element.
*/
private void click(int tabIndex) throws InterruptedException {
click(tab(tabIndex));
}

/*
* Click on the element.
*/
private void click(TestBenchElement element) throws InterruptedException {

element.click(10, 10);
if (DELAY > 0) {
sleep(DELAY);
}
}

/*
* Delay for PhantomJS.
*/
private final static int DELAY = 10;

private void assertSheet(int index) {
String labelCaption = "Tab " + index;

By id = By.id(TabKeyboardNavigation.labelID(index));
WebElement labelElement = getDriver().findElement(id);

waitForElementPresent(id);

Assert.assertEquals(labelCaption, labelCaption, labelElement.getText());
}

/*
* Provide the tab at specified index.
*/
private TestBenchElement tab(int index) {
By by = By.className("v-tabsheet-tabitemcell");

TestBenchElement element = (TestBenchElement) getDriver().findElements(
by).get(index - 1);

String expected = "Tab " + index;
Assert.assertEquals(expected,
element.getText().substring(0, expected.length()));

return element;
}

}

Loading…
Cancel
Save