Bug 1331154 - 9. Add TabsPanelComponent. r?sebastian
TabsPanelComponent and TabStripComponent share a lot of functionality, so factor
out TabsPresenterComponent.
MozReview-Commit-ID: Gnbo8gvowj6
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
@@ -44,17 +44,17 @@ public class TabStrip extends ThemedLine
public TabStrip(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
LayoutInflater.from(context).inflate(R.layout.tab_strip_inner, this);
tabStripView = (TabStripView) findViewById(R.id.tab_strip);
- addTabButton = (ThemedImageButton) findViewById(R.id.add_tab);
+ addTabButton = (ThemedImageButton) findViewById(R.id.tablet_add_tab);
addTabButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Tabs tabs = Tabs.getInstance();
if (isPrivateMode()) {
tabs.addPrivateTab();
} else {
tabs.addTab();
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
@@ -5,16 +5,17 @@
package org.mozilla.gecko.tabs;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.widget.ResizablePathDrawable;
import org.mozilla.gecko.widget.ResizablePathDrawable.NonScaledPathShape;
import org.mozilla.gecko.widget.themed.ThemedImageButton;
import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
import org.mozilla.gecko.widget.themed.ThemedTextView;
import org.json.JSONException;
import org.json.JSONObject;
@@ -103,16 +104,21 @@ public class TabStripItemView extends Th
}
final Tabs tabs = Tabs.getInstance();
tabs.closeTab(tabs.getTab(id), true);
}
});
}
+ @RobocopTarget
+ public int getTabId() {
+ return id;
+ }
+
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
// Queue a tab region update in the next draw() call. We don't
// update it immediately here because we need the new path from
// the background drawable to be updated first.
tabRegionNeedsUpdate = true;
--- a/mobile/android/base/resources/layout/tab_strip_inner.xml
+++ b/mobile/android/base/resources/layout/tab_strip_inner.xml
@@ -11,17 +11,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingTop="8dp"/>
<!-- The right margin creates a "dead area" on the right side of the button
which we compensate for with a touch delegate. See TabStrip -->
<org.mozilla.gecko.widget.themed.ThemedImageButton
- android:id="@+id/add_tab"
+ android:id="@+id/tablet_add_tab"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/tablet_tab_strip_height"
android:src="@drawable/tab_new"
gecko:drawableTintList="@color/tab_new_tab_strip_colors"
android:contentDescription="@string/new_tab"
android:layout_marginRight="9dp"
android:background="@drawable/tab_strip_button"/>
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/UITest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/UITest.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.tests;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.Driver;
import org.mozilla.gecko.tests.components.AboutHomeComponent;
import org.mozilla.gecko.tests.components.AppMenuComponent;
import org.mozilla.gecko.tests.components.BaseComponent;
import org.mozilla.gecko.tests.components.GeckoViewComponent;
import org.mozilla.gecko.tests.components.TabStripComponent;
+import org.mozilla.gecko.tests.components.TabsPanelComponent;
import org.mozilla.gecko.tests.components.ToolbarComponent;
import org.mozilla.gecko.tests.helpers.HelperInitializer;
import com.robotium.solo.Solo;
/**
* A base test class for Robocop (UI-centric) tests. This and the related classes attempt to
* provide a framework to improve upon the issues discovered with the previous BaseTest
@@ -32,16 +33,17 @@ abstract class UITest extends BaseRoboco
private static final String JUNIT_FAILURE_MSG = "A JUnit method was called. Make sure " +
"you are using AssertionHelper to make assertions. Try `fAssert*(...);`";
protected AboutHomeComponent mAboutHome;
protected AppMenuComponent mAppMenu;
protected GeckoViewComponent mGeckoView;
protected TabStripComponent mTabStrip;
protected ToolbarComponent mToolbar;
+ protected TabsPanelComponent mTabsPanel;
@Override
protected void setUp() throws Exception {
super.setUp();
// Helpers depend on components so initialize them first.
initComponents();
initHelpers();
@@ -52,16 +54,17 @@ abstract class UITest extends BaseRoboco
}
private void initComponents() {
mAboutHome = new AboutHomeComponent(this);
mAppMenu = new AppMenuComponent(this);
mGeckoView = new GeckoViewComponent(this);
mTabStrip = new TabStripComponent(this);
mToolbar = new ToolbarComponent(this);
+ mTabsPanel = new TabsPanelComponent(this);
}
private void initHelpers() {
HelperInitializer.init(this);
}
@Override
public Solo getSolo() {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
@@ -1,65 +1,90 @@
package org.mozilla.gecko.tests.components;
import android.view.View;
import android.support.v7.widget.RecyclerView;
import com.robotium.solo.Condition;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.tabs.TabStripItemView;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.tests.helpers.DeviceHelper;
import org.mozilla.gecko.tests.helpers.WaitHelper;
+import java.util.List;
+
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
/**
* A class representing any interactions that take place on the tablet tab strip.
*/
-public class TabStripComponent extends BaseComponent {
+public class TabStripComponent extends TabsPresenterComponent {
// Using a text id because the layout and therefore the id might be stripped from the (non-tablet) build
private static final String TAB_STRIP_ID = "tab_strip";
public TabStripComponent(final UITestContext testContext) {
super(testContext);
}
- public TabStripComponent assertTabCount(int count) {
- fAssertEquals("The tab strip tab count is " + count, count, getTabStripView().getAdapter().getItemCount());
- return this;
- }
-
public void switchToTab(int index) {
// The tab strip is only available on tablets
DeviceHelper.assertIsTablet();
View tabView = waitForTabView(index);
fAssertNotNull(String.format("Tab at index %d is not null", index), tabView);
mSolo.clickOnView(tabView);
}
+ @Override
+ public void clickNewTabButton() {
+ final int preNewTabCount = getTabCount();
+ mSolo.clickOnView(mSolo.getView(R.id.tablet_add_tab));
+ waitForNewTab(preNewTabCount);
+ }
+
+ private void waitForNewTab(final int tabCountBeforeNewTab) {
+ WaitHelper.waitFor("new tab", new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return getTabCount() == tabCountBeforeNewTab + 1;
+ }
+ });
+ }
+
+ @Override
+ protected void addVisibleTabIdsToList(List<Integer> idList) {
+ final RecyclerView tabsLayout = getPresenter();
+ final int tabsLayoutChildCount = tabsLayout.getChildCount();
+ for (int i = 0; i < tabsLayoutChildCount; i++) {
+ final TabStripItemView tabView = (TabStripItemView) tabsLayout.getChildAt(i);
+ idList.add(tabView.getTabId());
+ }
+ }
+
/**
* Note: this currently only supports the case where the tab strip visible tabs start at tab 0
* and the tab at {@code index} is visible in the tab strip.
*/
private View waitForTabView(final int index) {
- final RecyclerView tabStrip = getTabStripView();
+ final RecyclerView tabStrip = getPresenter();
final View[] tabView = new View[1];
WaitHelper.waitFor(String.format("Tab at index %d to be visible", index), new Condition() {
@Override
public boolean isSatisfied() {
return (tabView[0] = tabStrip.getChildAt(index)) != null;
}
});
return tabView[0];
}
- private RecyclerView getTabStripView() {
- RecyclerView tabStrip = (RecyclerView) mSolo.getView("tab_strip");
-
- fAssertNotNull("Tab strip is not null", tabStrip);
-
- return tabStrip;
+ /**
+ * You should use {@link TabsPresenterComponent#getPresenter()} instead if you expect the layout to exist.
+ */
+ @Override
+ protected RecyclerView maybeGetPresenter() {
+ return (RecyclerView) mSolo.getView("tab_strip");
}
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabsPanelComponent.java
@@ -0,0 +1,94 @@
+package org.mozilla.gecko.tests.components;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.robotium.solo.Condition;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.tabs.TabsLayoutItemView;
+import org.mozilla.gecko.tests.UITestContext;
+import org.mozilla.gecko.tests.helpers.WaitHelper;
+
+import java.util.List;
+
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
+
+public class TabsPanelComponent extends TabsPresenterComponent {
+ public TabsPanelComponent(final UITestContext testContext) {
+ super(testContext);
+ }
+
+ public void openPanel() {
+ assertTabsPanelIsClosed();
+ mSolo.clickOnView(mSolo.getView(R.id.tabs));
+ WaitHelper.waitFor("tabs panel to open", new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ final View tabsPanel = mSolo.getView(R.id.tabs_panel);
+ return tabsPanel != null && tabsPanel.getVisibility() == View.VISIBLE;
+ }
+ });
+ waitForAnimations();
+ }
+
+ public void closePanel() {
+ assertTabsPanelIsOpen();
+ mSolo.clickOnView(mSolo.getView(R.id.nav_back));
+ waitForTabsPanelToClose();
+ }
+
+ @Override
+ public void clickNewTabButton() {
+ assertTabsPanelIsOpen();
+ mSolo.clickOnView(mSolo.getView(R.id.add_tab));
+ waitForTabsPanelToClose();
+ }
+
+ private void waitForTabsPanelToClose() {
+ WaitHelper.waitFor("tabs panel to close", new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ final View tabsPanel = mSolo.getView(R.id.tabs_panel);
+ return tabsPanel.getVisibility() != View.VISIBLE;
+ }
+ });
+ waitForAnimations();
+ }
+
+ private void waitForAnimations() {
+ mSolo.sleep(1500);
+ }
+
+ @Override
+ protected void addVisibleTabIdsToList(List<Integer> idList) {
+ final RecyclerView tabsLayout = getPresenter();
+ final int tabsLayoutChildCount = tabsLayout.getChildCount();
+ for (int i = 0; i < tabsLayoutChildCount; i++) {
+ final TabsLayoutItemView tabView = (TabsLayoutItemView) tabsLayout.getChildAt(i);
+ idList.add(tabView.getTabId());
+ }
+ }
+
+ /**
+ * You should use {@link TabsPresenterComponent#getPresenter()} instead if you expect the layout to exist.
+ */
+ @Override
+ protected RecyclerView maybeGetPresenter() {
+ return (RecyclerView) mSolo.getView(R.id.normal_tabs);
+ }
+
+ private View getTabsPanel() {
+ return mSolo.getView(R.id.tabs_panel);
+ }
+
+ private void assertTabsPanelIsOpen() {
+ final View tabsPanel = getTabsPanel();
+ fAssertTrue("Tabs panel is open", tabsPanel != null && tabsPanel.getVisibility() == View.VISIBLE);
+ }
+
+ private void assertTabsPanelIsClosed() {
+ final View tabsPanel = getTabsPanel();
+ fAssertTrue("Tabs panel is closed", tabsPanel == null || tabsPanel.getVisibility() != View.VISIBLE);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabsPresenterComponent.java
@@ -0,0 +1,97 @@
+package org.mozilla.gecko.tests.components;
+
+import android.app.Instrumentation;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.Checkable;
+
+import org.mozilla.gecko.tests.UITestContext;
+import org.mozilla.gecko.tests.helpers.RobotiumHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertEquals;
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
+
+/**
+ * A component representing something that presents the list of tabs to the user along with a way
+ * to add tabs etc, like the tab strip on tablets or the tabs tray on phones and tablets.
+ */
+public abstract class TabsPresenterComponent extends BaseComponent {
+ public TabsPresenterComponent(final UITestContext testContext) {
+ super(testContext);
+ }
+
+ /**
+ * Assert that the tab at the given visual index is the one selected.
+ * @param tabIndex is the *visual* index of the tab to check (the first tab visible on screen is
+ * index 0).
+ */
+ public void assertSelectedTabIndexIs(int tabIndex) {
+ final RecyclerView tabsLayout = getPresenter();
+ fAssertTrue("The tab at tabIndex " + tabIndex + " is visible", tabIndex >= 0 && tabIndex < tabsLayout.getChildCount());
+ final Checkable tabView = (Checkable) tabsLayout.getChildAt(tabIndex);
+ fAssertTrue("The tab at tabIndex " + tabIndex + " is selected", tabView.isChecked());
+ }
+
+ public void assertTabCountIs(int count) {
+ fAssertEquals("The tab strip tab count is " + count, count, getTabCount());
+ }
+
+ /**
+ * Click the button used to create a new tab.
+ */
+ public abstract void clickNewTabButton();
+
+ /**
+ * The component must be currently active.
+ * @return the total tab count (not just those visible on screen).
+ */
+ public int getTabCount() {
+ return getPresenter().getAdapter().getItemCount();
+ }
+
+ /**
+ * @param index the *visual* index of the tab view to return (the first visible tab is index 0).
+ * @return the tab view at the given visual index.
+ */
+ public View getTabViewAt(int index) {
+ final RecyclerView presenter = getPresenter();
+ fAssertTrue("The tab at index " + index + " is visible", index >= 0 && index < presenter.getChildCount());
+ return presenter.getChildAt(index);
+ }
+
+ public List<Integer> getVisibleTabIdsInOrder() {
+ final ArrayList<Integer> tabIds = new ArrayList<>();
+ addVisibleTabIdsToList(tabIds);
+ return tabIds;
+ }
+
+ public void moveTab(int from, int to) {
+ final View fromTab = getTabViewAt(from);
+ final View toTab = getTabViewAt(to);
+ RobotiumHelper.longPressDragView(fromTab, toTab, true);
+ }
+
+ protected RecyclerView getPresenter() {
+ final RecyclerView tabsPresenter = maybeGetPresenter();
+
+ fAssertNotNull("Tabs presenter is not null", tabsPresenter);
+
+ return tabsPresenter;
+ }
+
+ /**
+ * (This method shouldn't really be necessary, but the tab strip and the tabs panel item views
+ * don't share a common base usable for this purpose.)
+ */
+ protected abstract void addVisibleTabIdsToList(List<Integer> idList);
+
+ /**
+ * Return the {@code RecyclerView} presenting tabs for this component, or {@code null} if the
+ * component isn't currently active.
+ */
+ protected abstract RecyclerView maybeGetPresenter();
+}
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTabStripPrivacyMode.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTabStripPrivacyMode.java
@@ -31,12 +31,12 @@ public class testTabStripPrivacyMode ext
// new private tab, but to prevent a false positive when the private tab does get added to
// the tab strip in error, sleep here to make sure the UI has time to update before we
// check it.
mSolo.sleep(250);
// Now make sure there's still only one tab in the tab strip, and that it's still the normal
// mode tab.
- mTabStrip.assertTabCount(1);
+ mTabStrip.assertTabCountIs(1);
mToolbar.assertTitle(normalModeUrl);
}
}