Bug 1331154 - 9. Add TabsPanelComponent. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Sat, 25 Feb 2017 19:28:05 -0600
changeset 493025 5ad210ca6b009bd0548218c817a77165802d4f75
parent 493024 0d77484768d23d34d65e89202c4eb10ab11d521b
child 493026 05fda79d5b94b6c5057c594cdb828ca26c17cfb4
push id47629
push userbmo:twointofive@gmail.com
push dateFri, 03 Mar 2017 05:50:49 +0000
reviewerssebastian
bugs1331154
milestone54.0a1
Bug 1331154 - 9. Add TabsPanelComponent. r?sebastian TabsPanelComponent and TabStripComponent share a lot of functionality, so factor out TabsPresenterComponent. MozReview-Commit-ID: Gnbo8gvowj6
mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
mobile/android/base/resources/layout/tab_strip_inner.xml
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/UITest.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabsPanelComponent.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabsPresenterComponent.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTabStripPrivacyMode.java
--- 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);
     }
  }