From 117c45ae1c3753e38c2ac41296ebc043c74b835e Mon Sep 17 00:00:00 2001 From: Bogdan Udrescu Date: Wed, 13 Aug 2014 10:02:42 +0300 Subject: [PATCH] Improve the selection tab algorithm after removing the selected tab (#6876) New protected method TabSheet.selectedTabIndexAfterTabRemove where anyone can implement his/hers own algorithm for selecting a new tab. Change-Id: I6a3dd62e7fc84e4dacb08d30d567f357678dd7e4 --- server/src/com/vaadin/ui/TabSheet.java | 55 ++++++++ .../tabsheet/NewSelectionAfterTabRemove.java | 61 +++++++++ .../NewSelectionAfterTabRemoveTest.java | 121 ++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemove.java create mode 100644 uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemoveTest.java diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java index 8b13ecf1a4..0e4ea0d034 100644 --- a/server/src/com/vaadin/ui/TabSheet.java +++ b/server/src/com/vaadin/ui/TabSheet.java @@ -193,6 +193,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, @Override public void removeComponent(Component component) { if (component != null && components.contains(component)) { + + int componentIndex = components.indexOf(component); + super.removeComponent(component); keyMapper.remove(component); components.remove(component); @@ -206,6 +209,24 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, if (components.isEmpty()) { setSelected(null); } else { + + int newSelectedIndex = selectedTabIndexAfterTabRemove(componentIndex); + + // Make sure the component actually exists, in case someone + // override it and provide a non existing component. + if (0 <= newSelectedIndex + && newSelectedIndex < components.size()) { + + Component newSelectedComponent = components + .get(newSelectedIndex); + + // Select only if the tab is enabled. + Tab newSelectedTab = tabs.get(newSelectedComponent); + if (newSelectedTab.isEnabled()) { + setSelected(newSelectedComponent); + } + } + // select the first enabled and visible tab, if any updateSelection(); fireSelectedTabChange(); @@ -215,6 +236,40 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, } } + /** + * Called when a selected tab is removed to specify the new tab to select. + * Can be overridden to provide another algorithm that calculates the new + * selection. + * + * Current implementation will choose the first enabled tab to the right of + * the removed tab if it's not the last one, otherwise will choose the + * closer enabled tab to the left. + * + * @since + * @param removedTabIndex + * the index of the selected tab which was just remove. + * @return the index of the tab to be selected or -1 if there are no more + * enabled tabs to select. + */ + protected int selectedTabIndexAfterTabRemove(int removedTabIndex) { + + for (int i = removedTabIndex; i < components.size(); i++) { + Tab tab = getTab(i); + if (tab.isEnabled()) { + return i; + } + } + + for (int i = removedTabIndex - 1; i >= 0; i--) { + Tab tab = getTab(i); + if (tab.isEnabled()) { + return i; + } + } + + return -1; + } + /** * Removes a {@link Tab} and the component associated with it, as previously * added with {@link #addTab(Component)}, diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemove.java b/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemove.java new file mode 100644 index 0000000000..939bd645ea --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemove.java @@ -0,0 +1,61 @@ +/* + * 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 com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Label; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TabSheet.Tab; + +/** + * In case a selected tab is removed the new selected one should be a neighbor. + * + * In case an unselected tab is removed and the selected one is not visible, the + * scroll should not jump to the selected one. + * + * @since + * @author Vaadin Ltd + */ +public class NewSelectionAfterTabRemove extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + TabSheet tabSheet = new TabSheet(); + + for (int i = 0; i < 20; i++) { + + String caption = "Tab " + i; + Label label = new Label(caption); + + Tab tab = tabSheet.addTab(label, caption); + tab.setClosable(true); + } + + addComponent(tabSheet); + } + + @Override + protected String getTestDescription() { + return "When a selected tab is removed, its neighbor should become selected."; + } + + @Override + protected Integer getTicketNumber() { + return 6876; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemoveTest.java b/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemoveTest.java new file mode 100644 index 0000000000..bffbc3ecdf --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/NewSelectionAfterTabRemoveTest.java @@ -0,0 +1,121 @@ +/* + * 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 java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * Automatic test of the default TabSheet selection algorithm when removing a + * selected tab. + * + * @since + * @author Vaadin Ltd + */ +public class NewSelectionAfterTabRemoveTest extends MultiBrowserTest { + + @Test + public void testSelection() throws IOException, InterruptedException { + openTestURL(); + + while (scrollRight()) { + } + + selectAndClose(tab(19)); + + Assert.assertTrue("Tab 18 selected", isTabSelected(tab(18))); + + selectAndClose(tab(16)); + + Assert.assertTrue("Tab 17 selected", isTabSelected(tab(17))); + } + + /* + * Select the specified tab and close it. + */ + private void selectAndClose(TestBenchElement tab) + throws InterruptedException { + tab.click(5, 5); + + sleep(10); + + tabClose(tab).click(2, 2); + + sleep(10); + } + + /* + * Gets the selected state of the specified tab. + */ + private boolean isTabSelected(TestBenchElement tab) { + return tab.getAttribute("class").contains( + "v-tabsheet-tabitemcell-selected") + && tab.getAttribute("class").contains( + "v-tabsheet-tabitemcell-focus"); + } + + /* + * Scroll the tabsheet bar to the right. + */ + private boolean scrollRight() { + List scrollElements = getDriver().findElements( + By.className("v-tabsheet-scrollerNext")); + if (!scrollElements.isEmpty()) { + TestBenchElement rightScrollElement = (TestBenchElement) scrollElements + .get(0); + rightScrollElement.click(5, 5); + return true; + } else { + return false; + } + } + + /* + * Provide the tab close button for the specified tab. + */ + private TestBenchElement tabClose(TestBenchElement tab) { + return (TestBenchElement) tab.findElement(By + .className("v-tabsheet-caption-close")); + } + + /* + * Provide the tab at specified index. + */ + private TestBenchElement tab(int index) { + By by = By.className("v-tabsheet-tabitemcell"); + + List tabs = getDriver().findElements(by); + + String expected = "Tab " + index; + + for (WebElement tab : tabs) { + if (tab.getText().startsWith(expected)) { + return (TestBenchElement) tab; + } + } + + return null; + } + +} -- 2.39.5