diff options
author | Bogdan Udrescu <bogdan@vaadin.com> | 2014-08-07 15:31:07 +0300 |
---|---|---|
committer | Bogdan Udrescu <bogdan@vaadin.com> | 2014-08-14 17:04:39 +0300 |
commit | 8c4ddbb2837c8966551636cf140bba86c9671d1e (patch) | |
tree | 44956315838bcd6a530a7d07edcb483a86fb84a7 | |
parent | ea8374649ba8b3b2d38a49a73eda205faf2f0fe9 (diff) | |
download | vaadin-framework-8c4ddbb2837c8966551636cf140bba86c9671d1e.tar.gz vaadin-framework-8c4ddbb2837c8966551636cf140bba86c9671d1e.zip |
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
5 files changed, 767 insertions, 404 deletions
diff --git a/WebContent/html-tests/ComponentFocus.html b/WebContent/html-tests/ComponentFocus.html new file mode 100644 index 0000000000..0a822520e0 --- /dev/null +++ b/WebContent/html-tests/ComponentFocus.html @@ -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>
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index 35fdebf353..a2a6889e15 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -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 @@ -282,6 +283,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @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() { diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html deleted file mode 100644 index 825988173a..0000000000 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html +++ /dev/null @@ -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> diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java index ba737f1df8..620f04fe60 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java @@ -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) { diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java new file mode 100644 index 0000000000..65307f9492 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java @@ -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; + } + +} |