1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.bookmarks;
6 
7 import static androidx.test.espresso.Espresso.onView;
8 import static androidx.test.espresso.action.ViewActions.click;
9 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
10 import static androidx.test.espresso.assertion.ViewAssertions.matches;
11 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
12 import static androidx.test.espresso.matcher.ViewMatchers.withText;
13 
14 import static org.mockito.Mockito.any;
15 import static org.mockito.Mockito.doNothing;
16 import static org.mockito.Mockito.when;
17 
18 import static org.chromium.components.browser_ui.widget.highlight.ViewHighlighterTestUtils.checkHighlightOff;
19 import static org.chromium.components.browser_ui.widget.highlight.ViewHighlighterTestUtils.checkHighlightPulse;
20 
21 import android.support.test.InstrumentationRegistry;
22 import android.support.test.runner.lifecycle.Stage;
23 import android.text.TextUtils;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.TextView;
27 
28 import androidx.annotation.Nullable;
29 import androidx.recyclerview.widget.RecyclerView;
30 import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
31 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
32 import androidx.test.filters.MediumTest;
33 import androidx.test.filters.SmallTest;
34 
35 import org.hamcrest.Matchers;
36 import org.hamcrest.core.IsInstanceOf;
37 import org.junit.After;
38 import org.junit.AfterClass;
39 import org.junit.Assert;
40 import org.junit.Before;
41 import org.junit.BeforeClass;
42 import org.junit.Rule;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.junit.MockitoJUnit;
47 import org.mockito.junit.MockitoRule;
48 
49 import org.chromium.base.ApplicationStatus;
50 import org.chromium.base.test.params.ParameterAnnotations;
51 import org.chromium.base.test.params.ParameterizedRunner;
52 import org.chromium.base.test.util.ApplicationTestUtils;
53 import org.chromium.base.test.util.CallbackHelper;
54 import org.chromium.base.test.util.CommandLineFlags;
55 import org.chromium.base.test.util.Criteria;
56 import org.chromium.base.test.util.CriteriaHelper;
57 import org.chromium.base.test.util.DisabledTest;
58 import org.chromium.base.test.util.Feature;
59 import org.chromium.base.test.util.FlakyTest;
60 import org.chromium.base.test.util.Restriction;
61 import org.chromium.chrome.R;
62 import org.chromium.chrome.browser.ChromeTabbedActivity;
63 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
64 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
65 import org.chromium.chrome.browser.bookmarks.BookmarkPromoHeader.PromoState;
66 import org.chromium.chrome.browser.flags.ChromeFeatureList;
67 import org.chromium.chrome.browser.flags.ChromeSwitches;
68 import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
69 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
70 import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
71 import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
72 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
73 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
74 import org.chromium.chrome.browser.profiles.Profile;
75 import org.chromium.chrome.browser.sync.AndroidSyncSettings;
76 import org.chromium.chrome.browser.sync.AndroidSyncSettings.AndroidSyncSettingsObserver;
77 import org.chromium.chrome.browser.tab.Tab;
78 import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
79 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
80 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
81 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
82 import org.chromium.chrome.test.util.ActivityUtils;
83 import org.chromium.chrome.test.util.BookmarkTestUtil;
84 import org.chromium.chrome.test.util.ChromeRenderTestRule;
85 import org.chromium.chrome.test.util.MenuUtils;
86 import org.chromium.chrome.test.util.browser.Features;
87 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
88 import org.chromium.components.bookmarks.BookmarkId;
89 import org.chromium.components.bookmarks.BookmarkType;
90 import org.chromium.components.browser_ui.widget.RecyclerViewTestUtils;
91 import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
92 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar;
93 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar.ViewType;
94 import org.chromium.components.embedder_support.util.UrlConstants;
95 import org.chromium.content_public.browser.test.util.TestThreadUtils;
96 import org.chromium.content_public.browser.test.util.TouchCommon;
97 import org.chromium.net.test.EmbeddedTestServer;
98 import org.chromium.ui.test.util.NightModeTestUtils;
99 import org.chromium.ui.test.util.UiRestriction;
100 
101 import java.util.ArrayList;
102 import java.util.List;
103 import java.util.concurrent.Callable;
104 import java.util.concurrent.ExecutionException;
105 
106 /**
107  * Tests for the bookmark manager.
108  */
109 // clang-format off
110 @RunWith(ParameterizedRunner.class)
111 @ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
112 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
113 public class BookmarkTest {
114     // clang-format on
115     @Rule
116     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
117 
118     @Rule
119     public ChromeRenderTestRule mRenderTestRule =
120             ChromeRenderTestRule.Builder.withPublicCorpus().build();
121     @Rule
122     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
123 
124     private static final String TEST_PAGE_URL_GOOGLE = "/chrome/test/data/android/google.html";
125     private static final String TEST_PAGE_TITLE_GOOGLE = "The Google";
126     private static final String TEST_PAGE_TITLE_GOOGLE2 = "Google";
127     private static final String TEST_PAGE_URL_FOO = "/chrome/test/data/android/test.html";
128     private static final String TEST_PAGE_TITLE_FOO = "Foo";
129     private static final String TEST_FOLDER_TITLE = "Test folder";
130     private static final String TEST_FOLDER_TITLE2 = "Test folder 2";
131     private static final String TEST_TITLE_A = "a";
132     private static final String TEST_URL_A = "http://a.com";
133 
134     private BookmarkManager mManager;
135     private BookmarkModel mBookmarkModel;
136     private BookmarkBridge mBookmarkBridge;
137     private RecyclerView mItemsContainer;
138     private String mTestPage;
139     private String mTestPageFoo;
140     private EmbeddedTestServer mTestServer;
141     private @Nullable BookmarkActivity mBookmarkActivity;
142     @Mock
143     private AndroidSyncSettings mAndroidSyncSettings;
144 
145     @BeforeClass
setUpBeforeActivityLaunched()146     public static void setUpBeforeActivityLaunched() {
147         ChromeNightModeTestUtils.setUpNightModeBeforeChromeActivityLaunched();
148     }
149 
150     @ParameterAnnotations.UseMethodParameterBefore(NightModeTestUtils.NightModeParams.class)
setupNightMode(boolean nightModeEnabled)151     public void setupNightMode(boolean nightModeEnabled) {
152         ChromeNightModeTestUtils.setUpNightModeForChromeActivity(nightModeEnabled);
153         mRenderTestRule.setNightModeEnabled(nightModeEnabled);
154     }
155 
156     @Before
setUp()157     public void setUp() {
158         mActivityTestRule.startMainActivityOnBlankPage();
159         TestThreadUtils.runOnUiThreadBlocking(() -> {
160             mBookmarkModel = new BookmarkModel(Profile.fromWebContents(
161                     mActivityTestRule.getActivity().getActivityTab().getWebContents()));
162             mBookmarkBridge = mActivityTestRule.getActivity().getBookmarkBridgeForTesting();
163 
164             // Stub AndroidSyncSettings state to make sure promos aren't suppressed.
165             when(mAndroidSyncSettings.doesMasterSyncSettingAllowChromeSync()).thenReturn(true);
166             when(mAndroidSyncSettings.isSyncEnabled()).thenReturn(false);
167             when(mAndroidSyncSettings.isChromeSyncEnabled()).thenReturn(false);
168             doNothing()
169                     .when(mAndroidSyncSettings)
170                     .registerObserver(any(AndroidSyncSettingsObserver.class));
171             doNothing()
172                     .when(mAndroidSyncSettings)
173                     .unregisterObserver(any(AndroidSyncSettingsObserver.class));
174             AndroidSyncSettings.overrideForTests(mAndroidSyncSettings);
175         });
176         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
177         mTestPage = mTestServer.getURL(TEST_PAGE_URL_GOOGLE);
178         mTestPageFoo = mTestServer.getURL(TEST_PAGE_URL_FOO);
179     }
180 
readPartnerBookmarks()181     private void readPartnerBookmarks() {
182         // Do not read partner bookmarks in setUp(), so that the lazy reading is covered.
183         TestThreadUtils.runOnUiThreadBlocking(
184                 () -> PartnerBookmarksShim.kickOffReading(mActivityTestRule.getActivity()));
185         BookmarkTestUtil.waitForBookmarkModelLoaded();
186     }
187 
188     /**
189      * Loads an empty partner bookmarks folder for testing. The partner bookmarks folder will appear
190      * in the mobile bookmarks folder.
191      *
192      */
loadEmptyPartnerBookmarksForTesting()193     private void loadEmptyPartnerBookmarksForTesting() {
194         TestThreadUtils.runOnUiThreadBlocking(
195                 () -> { mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting(); });
196         BookmarkTestUtil.waitForBookmarkModelLoaded();
197     }
198 
199     /**
200      * Loads a non-empty partner bookmarks folder for testing. The partner bookmarks folder will
201      * appear in the mobile bookmarks folder.
202      */
loadFakePartnerBookmarkShimForTesting()203     private void loadFakePartnerBookmarkShimForTesting() {
204         TestThreadUtils.runOnUiThreadBlocking(
205                 () -> { mBookmarkModel.loadFakePartnerBookmarkShimForTesting(); });
206         BookmarkTestUtil.waitForBookmarkModelLoaded();
207     }
208 
209     @After
tearDown()210     public void tearDown() {
211         if (mTestServer != null) mTestServer.stopAndDestroyServer();
212     }
213 
214     @AfterClass
tearDownAfterActivityDestroyed()215     public static void tearDownAfterActivityDestroyed() {
216         ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
217     }
218 
openBookmarkManager()219     private void openBookmarkManager() throws InterruptedException {
220         if (mActivityTestRule.getActivity().isTablet()) {
221             mActivityTestRule.loadUrl(UrlConstants.BOOKMARKS_URL);
222             mItemsContainer = mActivityTestRule.getActivity().findViewById(R.id.recycler_view);
223             mItemsContainer.setItemAnimator(null); // Disable animation to reduce flakiness.
224             mManager = ((BookmarkPage) mActivityTestRule.getActivity()
225                                 .getActivityTab()
226                                 .getNativePage())
227                                .getManagerForTesting();
228         } else {
229             // phone
230             mBookmarkActivity = ActivityUtils.waitForActivity(
231                     InstrumentationRegistry.getInstrumentation(), BookmarkActivity.class,
232                     new MenuUtils.MenuActivityTrigger(InstrumentationRegistry.getInstrumentation(),
233                             mActivityTestRule.getActivity(), R.id.all_bookmarks_menu_id));
234             mItemsContainer = mBookmarkActivity.findViewById(R.id.recycler_view);
235             mItemsContainer.setItemAnimator(null); // Disable animation to reduce flakiness.
236             mManager = mBookmarkActivity.getManagerForTesting();
237         }
238 
239         TestThreadUtils.runOnUiThreadBlocking(
240                 () -> mManager.getDragStateDelegate().setA11yStateForTesting(false));
241     }
242 
isItemPresentInBookmarkList(final String expectedTitle)243     private boolean isItemPresentInBookmarkList(final String expectedTitle) {
244         return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
245             @Override
246             public Boolean call() {
247                 for (int i = 0; i < mItemsContainer.getAdapter().getItemCount(); i++) {
248                     BookmarkId item = getIdByPosition(i);
249 
250                     if (item == null) continue;
251 
252                     String actualTitle = mBookmarkModel.getBookmarkTitle(item);
253                     if (TextUtils.equals(actualTitle, expectedTitle)) {
254                         return true;
255                     }
256                 }
257                 return false;
258             }
259         });
260     }
261 
262     private void waitForOfflinePageSaved(String url) throws Exception {
263         CallbackHelper callbackHelper = new CallbackHelper();
264         TestThreadUtils.runOnUiThreadBlocking(() -> {
265             OfflinePageBridge bridge =
266                     OfflinePageBridge.getForProfile(Profile.getLastUsedRegularProfile());
267             bridge.getAllPages((items) -> {
268                 for (OfflinePageItem item : items) {
269                     if (url.startsWith(item.getUrl())) {
270                         callbackHelper.notifyCalled();
271                     }
272                 }
273             });
274         });
275         callbackHelper.waitForFirst();
276     }
277 
278     @Test
279     @SmallTest
testAddBookmark()280     public void testAddBookmark() throws Exception {
281         mActivityTestRule.loadUrl(mTestPage);
282         // Check partner bookmarks are lazily loaded.
283         Assert.assertFalse(mBookmarkModel.isBookmarkModelLoaded());
284 
285         // Click star button to bookmark the current tab.
286         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
287                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
288         BookmarkTestUtil.waitForBookmarkModelLoaded();
289         // All actions with BookmarkModel needs to run on UI thread.
290         TestThreadUtils.runOnUiThreadBlocking(() -> {
291             long bookmarkIdLong = mBookmarkBridge.getUserBookmarkIdForTab(
292                     mActivityTestRule.getActivity().getActivityTabProvider().get());
293             BookmarkId id = new BookmarkId(bookmarkIdLong, BookmarkType.NORMAL);
294             Assert.assertTrue("The test page is not added as bookmark: ",
295                     mBookmarkModel.doesBookmarkExist(id));
296             BookmarkItem item = mBookmarkModel.getBookmarkById(id);
297             Assert.assertEquals(mBookmarkModel.getDefaultFolder(), item.getParentId());
298             Assert.assertEquals(mTestPage, item.getUrl());
299             Assert.assertEquals(TEST_PAGE_TITLE_GOOGLE, item.getTitle());
300         });
301 
302         waitForOfflinePageSaved(mTestPage);
303 
304         // Click the star button again to launch the edit activity.
305         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
306                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
307         waitForEditActivity();
308     }
309 
310     @Test
311     @SmallTest
testAddBookmarkSnackbar()312     public void testAddBookmarkSnackbar() {
313         mActivityTestRule.loadUrl(mTestPage);
314         // Check partner bookmarks are lazily loaded.
315         Assert.assertFalse(mBookmarkModel.isBookmarkModelLoaded());
316         // Arbitrarily long duration to validate appearance of snackbar.
317         SnackbarManager.setDurationForTesting(50000);
318 
319         // Click star button to bookmark the current tab.
320         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
321                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
322         BookmarkTestUtil.waitForBookmarkModelLoaded();
323         // All actions with BookmarkModel needs to run on UI thread.
324         TestThreadUtils.runOnUiThreadBlocking(() -> {
325             long bookmarkIdLong = mBookmarkBridge.getUserBookmarkIdForTab(
326                     mActivityTestRule.getActivity().getActivityTabProvider().get());
327             BookmarkId id = new BookmarkId(bookmarkIdLong, BookmarkType.NORMAL);
328             Assert.assertTrue("The test page is not added as bookmark: ",
329                     mBookmarkModel.doesBookmarkExist(id));
330 
331             SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
332             Snackbar currentSnackbar = snackbarManager.getCurrentSnackbarForTesting();
333             Assert.assertEquals("Add bookmark snackbar not shown.", Snackbar.UMA_BOOKMARK_ADDED,
334                     currentSnackbar.getIdentifierForTesting());
335             currentSnackbar.getController().onAction(null);
336         });
337 
338         waitForEditActivity();
339         SnackbarManager.setDurationForTesting(0);
340     }
341 
342     @Test
343     @SmallTest
testAddBookmarkToOtherFolder()344     public void testAddBookmarkToOtherFolder() {
345         mActivityTestRule.loadUrl(mTestPage);
346         readPartnerBookmarks();
347         // Set default folder as "Other Folder".
348         TestThreadUtils.runOnUiThreadBlocking(() -> {
349             SharedPreferencesManager.getInstance().writeString(
350                     ChromePreferenceKeys.BOOKMARKS_LAST_USED_PARENT,
351                     mBookmarkModel.getOtherFolderId().toString());
352         });
353         // Click star button to bookmark the current tab.
354         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
355                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
356         BookmarkTestUtil.waitForBookmarkModelLoaded();
357         // All actions with BookmarkModel needs to run on UI thread.
358         TestThreadUtils.runOnUiThreadBlocking(() -> {
359             long bookmarkIdLong = mBookmarkBridge.getUserBookmarkIdForTab(
360                     mActivityTestRule.getActivity().getActivityTabProvider().get());
361             BookmarkId id = new BookmarkId(bookmarkIdLong, BookmarkType.NORMAL);
362             Assert.assertTrue("The test page is not added as bookmark: ",
363                     mBookmarkModel.doesBookmarkExist(id));
364             BookmarkItem item = mBookmarkModel.getBookmarkById(id);
365             Assert.assertEquals("Bookmark added in a wrong default folder.",
366                     mBookmarkModel.getOtherFolderId(), item.getParentId());
367         });
368     }
369 
370     @Test
371     @SmallTest
testOpenBookmark()372     public void testOpenBookmark() throws InterruptedException, ExecutionException {
373         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage);
374         openBookmarkManager();
375         Assert.assertTrue("Grid view does not contain added bookmark: ",
376                 isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
377         final View title = getViewWithText(mItemsContainer, TEST_PAGE_TITLE_GOOGLE);
378         TestThreadUtils.runOnUiThreadBlocking(() -> TouchCommon.singleClickView(title));
379         ChromeTabbedActivity activity = waitForTabbedActivity();
380         CriteriaHelper.pollUiThread(() -> {
381             Tab activityTab = activity.getActivityTab();
382             Criteria.checkThat(activityTab, Matchers.notNullValue());
383             Criteria.checkThat(activityTab.getUrl(), Matchers.notNullValue());
384             Criteria.checkThat(activityTab.getUrl().getSpec(), Matchers.is(mTestPage));
385         });
386     }
387 
388     @Test
389     @SmallTest
testUrlComposition()390     public void testUrlComposition() {
391         readPartnerBookmarks();
392         TestThreadUtils.runOnUiThreadBlocking(() -> {
393             BookmarkId mobileId = mBookmarkModel.getMobileFolderId();
394             BookmarkId bookmarkBarId = mBookmarkModel.getDesktopFolderId();
395             BookmarkId otherId = mBookmarkModel.getOtherFolderId();
396             Assert.assertEquals("chrome-native://bookmarks/folder/" + mobileId,
397                     BookmarkUIState.createFolderUrl(mobileId).toString());
398             Assert.assertEquals("chrome-native://bookmarks/folder/" + bookmarkBarId,
399                     BookmarkUIState.createFolderUrl(bookmarkBarId).toString());
400             Assert.assertEquals("chrome-native://bookmarks/folder/" + otherId,
401                     BookmarkUIState.createFolderUrl(otherId).toString());
402         });
403     }
404 
405     @Test
406     @SmallTest
407     @FlakyTest(message = "crbug.com/879803")
testOpenBookmarkManager()408     public void testOpenBookmarkManager() throws InterruptedException {
409         openBookmarkManager();
410         BookmarkDelegate delegate = getBookmarkManager();
411 
412         Assert.assertEquals(BookmarkUIState.STATE_FOLDER, delegate.getCurrentState());
413         Assert.assertEquals("chrome-native://bookmarks/folder/3",
414                 BookmarkUtils.getLastUsedUrl(mActivityTestRule.getActivity()));
415     }
416 
417     @Test
418     @MediumTest
419     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
testFolderNavigation_Phone()420     public void testFolderNavigation_Phone() throws InterruptedException, ExecutionException {
421         BookmarkId testFolder = addFolder(TEST_FOLDER_TITLE);
422         openBookmarkManager();
423         final BookmarkDelegate delegate = getBookmarkManager();
424         final BookmarkActionBar toolbar = ((BookmarkManager) delegate).getToolbarForTests();
425 
426         // Open the "Mobile bookmarks" folder.
427         TestThreadUtils.runOnUiThreadBlocking(
428                 () -> delegate.openFolder(mBookmarkModel.getMobileFolderId()));
429 
430         // Check that we are in the mobile bookmarks folder.
431         Assert.assertEquals("Mobile bookmarks", toolbar.getTitle());
432         Assert.assertEquals(SelectableListToolbar.NAVIGATION_BUTTON_BACK,
433                 toolbar.getNavigationButtonForTests());
434         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
435 
436         // Open the new test folder.
437         TestThreadUtils.runOnUiThreadBlocking(() -> delegate.openFolder(testFolder));
438 
439         // Check that we are in the editable test folder.
440         Assert.assertEquals(TEST_FOLDER_TITLE, toolbar.getTitle());
441         Assert.assertEquals(SelectableListToolbar.NAVIGATION_BUTTON_BACK,
442                 toolbar.getNavigationButtonForTests());
443         Assert.assertTrue(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
444 
445         // Call BookmarkActionBar#onClick() to activate the navigation button.
446         TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
447 
448         // Check that we are back in the mobile folder
449         Assert.assertEquals("Mobile bookmarks", toolbar.getTitle());
450         Assert.assertEquals(SelectableListToolbar.NAVIGATION_BUTTON_BACK,
451                 toolbar.getNavigationButtonForTests());
452         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
453 
454         // Call BookmarkActionBar#onClick() to activate the navigation button.
455         TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
456 
457         // Check that we are in the root folder.
458         Assert.assertEquals("Bookmarks", toolbar.getTitle());
459         Assert.assertEquals(SelectableListToolbar.NAVIGATION_BUTTON_NONE,
460                 toolbar.getNavigationButtonForTests());
461         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
462     }
463 
464     // TODO(twellington): Write a folder navigation test for tablets that waits for the Tab hosting
465     //                    the native page to update its url after navigations.
466 
467     @Test
468     @MediumTest
testSearchBookmarks()469     public void testSearchBookmarks() throws Exception {
470         BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
471         BookmarkId folder = addFolder(TEST_FOLDER_TITLE);
472         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage, folder);
473         addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, folder);
474         openBookmarkManager();
475 
476         RecyclerView.Adapter adapter = getAdapter();
477         final BookmarkDelegate delegate = getBookmarkManager();
478 
479         // Open the new folder where these bookmarks were created.
480         openFolder(folder);
481 
482         Assert.assertEquals(BookmarkUIState.STATE_FOLDER, delegate.getCurrentState());
483         Assert.assertEquals(
484                 "Wrong number of items before starting search.", 3, adapter.getItemCount());
485 
486         TestThreadUtils.runOnUiThreadBlocking(delegate::openSearchUI);
487 
488         Assert.assertEquals(BookmarkUIState.STATE_SEARCHING, delegate.getCurrentState());
489         Assert.assertEquals(
490                 "Wrong number of items after showing search UI. The promo should be hidden.", 2,
491                 adapter.getItemCount());
492 
493         searchBookmarks("Google");
494         Assert.assertEquals("Wrong number of items after searching.", 1,
495                 mItemsContainer.getAdapter().getItemCount());
496 
497         BookmarkId newBookmark = addBookmark(TEST_PAGE_TITLE_GOOGLE2, mTestPage);
498         Assert.assertEquals("Wrong number of items after bookmark added while searching.", 2,
499                 mItemsContainer.getAdapter().getItemCount());
500 
501         removeBookmark(newBookmark);
502         Assert.assertEquals("Wrong number of items after bookmark removed while searching.", 1,
503                 mItemsContainer.getAdapter().getItemCount());
504 
505         searchBookmarks("Non-existent page");
506         Assert.assertEquals("Wrong number of items after searching for non-existent item.", 0,
507                 mItemsContainer.getAdapter().getItemCount());
508 
509         TestThreadUtils.runOnUiThreadBlocking(
510                 () -> ((BookmarkManager) delegate).getToolbarForTests().hideSearchView());
511         Assert.assertEquals("Wrong number of items after closing search UI.", 3,
512                 mItemsContainer.getAdapter().getItemCount());
513         Assert.assertEquals(BookmarkUIState.STATE_FOLDER, delegate.getCurrentState());
514     }
515 
516     @Test
517     @MediumTest
testSearchBookmarks_Delete()518     public void testSearchBookmarks_Delete() throws Exception {
519         BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_NONE);
520         BookmarkId testFolder = addFolder(TEST_FOLDER_TITLE);
521         BookmarkId testFolder2 = addFolder(TEST_FOLDER_TITLE2, testFolder);
522         BookmarkId testBookmark = addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage, testFolder);
523         addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, testFolder);
524         openBookmarkManager();
525 
526         RecyclerView.Adapter adapter = getAdapter();
527         BookmarkManager manager = getBookmarkManager();
528 
529         // Open the new folder where these bookmarks were created.
530         openFolder(testFolder);
531 
532         Assert.assertEquals("Wrong state, should be in folder", BookmarkUIState.STATE_FOLDER,
533                 manager.getCurrentState());
534         Assert.assertEquals(
535                 "Wrong number of items before starting search.", 3, adapter.getItemCount());
536 
537         // Start searching without entering a query.
538         TestThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
539         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
540         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
541                 manager.getCurrentState());
542 
543         // Select testFolder2 and delete it.
544         toggleSelectionAndEndAnimation(testFolder2,
545                 (BookmarkRow) mItemsContainer.findViewHolderForLayoutPosition(2).itemView);
546         TestThreadUtils.runOnUiThreadBlocking(
547                 ()
548                         -> manager.getToolbarForTests().onMenuItemClick(
549                                 manager.getToolbarForTests().getMenu().findItem(
550                                         R.id.selection_mode_delete_menu_id)));
551 
552         // Search should be exited and the folder should be gone.
553         Assert.assertEquals("Wrong state, should be in folder", BookmarkUIState.STATE_FOLDER,
554                 manager.getCurrentState());
555         Assert.assertEquals(
556                 "Wrong number of items before starting search.", 2, adapter.getItemCount());
557 
558         // Start searching, enter a query.
559         TestThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
560         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
561                 manager.getCurrentState());
562         searchBookmarks("Google");
563         Assert.assertEquals("Wrong number of items after searching.", 1,
564                 mItemsContainer.getAdapter().getItemCount());
565 
566         // Remove the bookmark.
567         removeBookmark(testBookmark);
568 
569         // The user should still be searching, and the bookmark should be gone.
570         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
571                 manager.getCurrentState());
572         Assert.assertEquals("Wrong number of items after searching.", 0,
573                 mItemsContainer.getAdapter().getItemCount());
574 
575         // Undo the deletion.
576         TestThreadUtils.runOnUiThreadBlocking(
577                 () -> manager.getUndoControllerForTests().onAction(null));
578 
579         // The user should still be searching, and the bookmark should reappear.
580         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
581                 manager.getCurrentState());
582         Assert.assertEquals("Wrong number of items after searching.", 1,
583                 mItemsContainer.getAdapter().getItemCount());
584     }
585 
586     @Test
587     @MediumTest
588     @Feature({"RenderTest"})
589     @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
testBookmarkFolderIcon(boolean nightModeEnabled)590     public void testBookmarkFolderIcon(boolean nightModeEnabled) throws Exception {
591         BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_NONE);
592         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
593         openBookmarkManager();
594 
595         RecyclerView.Adapter adapter = getAdapter();
596         final BookmarkManager manager = getBookmarkManager();
597 
598         mRenderTestRule.render(manager.getView(), "bookmark_manager_one_folder");
599 
600         BookmarkRow itemView = (BookmarkRow) manager.getRecyclerViewForTests()
601                                        .findViewHolderForAdapterPosition(0)
602                                        .itemView;
603 
604         toggleSelectionAndEndAnimation(getIdByPosition(0), itemView);
605 
606         // Make sure the Item "test" is selected.
607         CriteriaHelper.pollUiThread(
608                 itemView::isChecked, "Expected item \"test\" to become selected");
609 
610         mRenderTestRule.render(manager.getView(), "bookmark_manager_folder_selected");
611         toggleSelectionAndEndAnimation(getIdByPosition(0), itemView);
612         mRenderTestRule.render(manager.getView(), "bookmark_manager_one_folder");
613     }
614 
615     @Test
616     @MediumTest
617     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE}) // Tablets don't have a close button.
testCloseBookmarksWhileStillLoading()618     public void testCloseBookmarksWhileStillLoading() throws Exception {
619         BookmarkManager.preventLoadingForTesting(true);
620 
621         openBookmarkManager();
622 
623         final BookmarkActionBar toolbar = mManager.getToolbarForTests();
624 
625         TestThreadUtils.runOnUiThreadBlocking(
626                 () -> toolbar.onMenuItemClick(toolbar.getMenu().findItem(R.id.close_menu_id)));
627 
628         ApplicationTestUtils.waitForActivityState(mBookmarkActivity, Stage.DESTROYED);
629 
630         BookmarkManager.preventLoadingForTesting(false);
631     }
632 
633     @Test
634     @MediumTest
testEditHiddenWhileStillLoading()635     public void testEditHiddenWhileStillLoading() throws Exception {
636         BookmarkManager.preventLoadingForTesting(true);
637 
638         openBookmarkManager();
639 
640         BookmarkActionBar toolbar = mManager.getToolbarForTests();
641         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
642 
643         BookmarkManager.preventLoadingForTesting(false);
644     }
645 
646     @Test
647     @MediumTest
testEndIconVisibilityInSelectionMode()648     public void testEndIconVisibilityInSelectionMode() throws Exception {
649         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
650         addBookmark(TEST_TITLE_A, TEST_URL_A);
651 
652         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
653         openBookmarkManager();
654 
655         BookmarkRow test =
656                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
657         View testMoreButton = test.findViewById(R.id.more);
658         View testDragHandle = test.getDragHandleViewForTests();
659 
660         BookmarkRow testFolderA =
661                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
662         View aMoreButton = testFolderA.findViewById(R.id.more);
663         View aDragHandle = testFolderA.getDragHandleViewForTests();
664 
665         toggleSelectionAndEndAnimation(testId, test);
666 
667         // Callback occurs when Item "test" is selected.
668         CriteriaHelper.pollUiThread(test::isChecked, "Expected item \"test\" to become selected");
669 
670         Assert.assertEquals("Expected bookmark toolbar to be selection mode",
671                 mManager.getToolbarForTests().getCurrentViewType(), ViewType.SELECTION_VIEW);
672         Assert.assertEquals("Expected more button of selected item to be gone when drag is active.",
673                 View.GONE, testMoreButton.getVisibility());
674         Assert.assertEquals(
675                 "Expected drag handle of selected item to be visible when drag is active.",
676                 View.VISIBLE, testDragHandle.getVisibility());
677         Assert.assertTrue("Expected drag handle to be enabled when drag is active.",
678                 testDragHandle.isEnabled());
679 
680         Assert.assertEquals(
681                 "Expected more button of unselected item to be gone when drag is active.",
682                 View.GONE, aMoreButton.getVisibility());
683         Assert.assertEquals(
684                 "Expected drag handle of unselected item to be visible when drag is active.",
685                 View.VISIBLE, aDragHandle.getVisibility());
686         Assert.assertFalse(
687                 "Expected drag handle of unselected item to be disabled when drag is active.",
688                 aDragHandle.isEnabled());
689     }
690 
691     @Test
692     @MediumTest
693     @FlakyTest(message = "crbug.com/1075804")
testEndIconVisiblityInSearchMode()694     public void testEndIconVisiblityInSearchMode() throws Exception {
695         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
696         addFolder(TEST_TITLE_A);
697 
698         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
699         openBookmarkManager();
700 
701         View searchButton = mManager.getToolbarForTests().findViewById(R.id.search_menu_id);
702 
703         BookmarkRow test =
704                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
705         View testMoreButton = test.findViewById(R.id.more);
706         View testDragHandle = test.getDragHandleViewForTests();
707 
708         BookmarkRow a = (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
709         View aMoreButton = a.findViewById(R.id.more);
710         View aDragHandle = a.getDragHandleViewForTests();
711 
712         TestThreadUtils.runOnUiThreadBlocking(searchButton::performClick);
713 
714         // Callback occurs when Item "test" is selected.
715         CriteriaHelper.pollUiThread(
716                 () -> mManager.getToolbarForTests().isSearching(), "Expected to enter search mode");
717 
718         toggleSelectionAndEndAnimation(testId, test);
719 
720         // Callback occurs when Item "test" is selected.
721         CriteriaHelper.pollUiThread(test::isChecked, "Expected item \"test\" to become selected");
722 
723         Assert.assertEquals("Expected drag handle of selected item to be gone "
724                         + "when selection mode is activated from search.",
725                 View.GONE, testDragHandle.getVisibility());
726         Assert.assertEquals("Expected more button of selected item to be visible "
727                         + "when selection mode is activated from search.",
728                 View.VISIBLE, testMoreButton.getVisibility());
729         Assert.assertFalse("Expected more button of selected item to be disabled "
730                         + "when selection mode is activated from search.",
731                 testMoreButton.isEnabled());
732 
733         Assert.assertEquals("Expected drag handle of unselected item to be gone "
734                         + "when selection mode is activated from search.",
735                 View.GONE, aDragHandle.getVisibility());
736         Assert.assertEquals("Expected more button of unselected item to be visible "
737                         + "when selection mode is activated from search.",
738                 View.VISIBLE, aMoreButton.getVisibility());
739         Assert.assertFalse("Expected more button of unselected item to be disabled "
740                         + "when selection mode is activated from search.",
741                 aMoreButton.isEnabled());
742     }
743 
744     @Test
745     @MediumTest
testSmallDrag_Up_BookmarksOnly()746     public void testSmallDrag_Up_BookmarksOnly() throws Exception {
747         List<BookmarkId> initial = new ArrayList<>();
748         List<BookmarkId> expected = new ArrayList<>();
749         BookmarkId fooId = addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo);
750         BookmarkId googleId = addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage);
751         BookmarkId aId = addBookmark(TEST_TITLE_A, TEST_URL_A);
752 
753         // When bookmarks are added, they are added to the top of the list.
754         // The current bookmark order is the reverse of the order in which they were added.
755         initial.add(aId);
756         initial.add(googleId);
757         initial.add(fooId);
758 
759         TestThreadUtils.runOnUiThreadBlocking(() -> {
760             Assert.assertEquals("Bookmarks were not added in the expected order.", initial,
761                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder()).subList(0, 3));
762         });
763 
764         expected.add(fooId);
765         expected.add(aId);
766         expected.add(googleId);
767 
768         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
769         openBookmarkManager();
770 
771         // Callback occurs upon changes inside of the bookmark model.
772         CallbackHelper modelReorderHelper = new CallbackHelper();
773         BookmarkBridge.BookmarkModelObserver bookmarkModelObserver =
774                 new BookmarkBridge.BookmarkModelObserver() {
775                     @Override
776                     public void bookmarkModelChanged() {
777                         modelReorderHelper.notifyCalled();
778                     }
779                 };
780 
781         // Perform registration to make callbacks work.
782         TestThreadUtils.runOnUiThreadBlocking(
783                 () -> { mBookmarkModel.addObserver(bookmarkModelObserver); });
784 
785         BookmarkRow foo =
786                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(3).itemView;
787         Assert.assertEquals("Wrong bookmark item selected.", TEST_PAGE_TITLE_FOO, foo.getTitle());
788         toggleSelectionAndEndAnimation(fooId, foo);
789 
790         // Starts as last bookmark (2nd index) and ends as 0th bookmark (promo header not included).
791         TestThreadUtils.runOnUiThreadBlocking(() -> {
792             ((BookmarkItemsAdapter) mItemsContainer.getAdapter()).simulateDragForTests(3, 1);
793         });
794 
795         modelReorderHelper.waitForCallback(0, 1);
796         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
797 
798         TestThreadUtils.runOnUiThreadBlocking(() -> {
799             List<BookmarkId> observed =
800                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder());
801             // Exclude partner bookmarks folder
802             Assert.assertEquals(expected, observed.subList(0, 3));
803             Assert.assertTrue("The selected item should stay selected", foo.isItemSelected());
804         });
805     }
806 
807     @Test
808     @MediumTest
testSmallDrag_Down_FoldersOnly()809     public void testSmallDrag_Down_FoldersOnly() throws Exception {
810         List<BookmarkId> initial = new ArrayList<>();
811         List<BookmarkId> expected = new ArrayList<>();
812         BookmarkId aId = addFolder("a");
813         BookmarkId bId = addFolder("b");
814         BookmarkId cId = addFolder("c");
815         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
816 
817         initial.add(testId);
818         initial.add(cId);
819         initial.add(bId);
820         initial.add(aId);
821 
822         TestThreadUtils.runOnUiThreadBlocking(() -> {
823             Assert.assertEquals("Bookmarks were not added in the expected order.", initial,
824                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder()).subList(0, 4));
825         });
826 
827         expected.add(cId);
828         expected.add(bId);
829         expected.add(aId);
830         expected.add(testId);
831 
832         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
833         openBookmarkManager();
834 
835         // Callback occurs upon changes inside of the bookmark model.
836         CallbackHelper modelReorderHelper = new CallbackHelper();
837         BookmarkBridge.BookmarkModelObserver bookmarkModelObserver =
838                 new BookmarkBridge.BookmarkModelObserver() {
839                     @Override
840                     public void bookmarkModelChanged() {
841                         modelReorderHelper.notifyCalled();
842                     }
843                 };
844 
845         // Perform registration to make callbacks work.
846         TestThreadUtils.runOnUiThreadBlocking(
847                 () -> { mBookmarkModel.addObserver(bookmarkModelObserver); });
848 
849         BookmarkFolderRow test =
850                 (BookmarkFolderRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
851         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE, test.getTitle());
852 
853         toggleSelectionAndEndAnimation(testId, test);
854 
855         // Starts as 0th bookmark (not counting promo header) and ends as last (index 3).
856         TestThreadUtils.runOnUiThreadBlocking(() -> {
857             ((BookmarkItemsAdapter) mItemsContainer.getAdapter()).simulateDragForTests(1, 4);
858         });
859 
860         modelReorderHelper.waitForCallback(0, 1);
861         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
862 
863         TestThreadUtils.runOnUiThreadBlocking(() -> {
864             List<BookmarkId> observed =
865                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder());
866             // Exclude partner bookmarks folder
867             Assert.assertEquals(expected, observed.subList(0, 4));
868             Assert.assertTrue("The selected item should stay selected", test.isItemSelected());
869         });
870     }
871 
872     @Test
873     @MediumTest
testSmallDrag_Down_MixedFoldersAndBookmarks()874     public void testSmallDrag_Down_MixedFoldersAndBookmarks() throws Exception {
875         List<BookmarkId> initial = new ArrayList<>();
876         List<BookmarkId> expected = new ArrayList<>();
877         BookmarkId aId = addFolder("a");
878         BookmarkId bId = addBookmark("b", "http://b.com");
879         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
880 
881         initial.add(testId);
882         initial.add(bId);
883         initial.add(aId);
884 
885         TestThreadUtils.runOnUiThreadBlocking(() -> {
886             Assert.assertEquals("Bookmarks were not added in the expected order.", initial,
887                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder()).subList(0, 3));
888         });
889 
890         expected.add(bId);
891         expected.add(testId);
892         expected.add(aId);
893 
894         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
895         openBookmarkManager();
896 
897         // Callback occurs upon changes inside of the bookmark model.
898         CallbackHelper modelReorderHelper = new CallbackHelper();
899         BookmarkBridge.BookmarkModelObserver bookmarkModelObserver =
900                 new BookmarkBridge.BookmarkModelObserver() {
901                     @Override
902                     public void bookmarkModelChanged() {
903                         modelReorderHelper.notifyCalled();
904                     }
905                 };
906         // Perform registration to make callbacks work.
907         TestThreadUtils.runOnUiThreadBlocking(
908                 () -> { mBookmarkModel.addObserver(bookmarkModelObserver); });
909 
910         BookmarkFolderRow test =
911                 (BookmarkFolderRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
912         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE, test.getTitle());
913 
914         toggleSelectionAndEndAnimation(testId, test);
915 
916         // Starts as 0th bookmark (not counting promo header) and ends at the 1st index.
917         TestThreadUtils.runOnUiThreadBlocking(() -> {
918             ((BookmarkItemsAdapter) mItemsContainer.getAdapter()).simulateDragForTests(1, 2);
919         });
920 
921         modelReorderHelper.waitForCallback(0, 1);
922         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
923 
924         TestThreadUtils.runOnUiThreadBlocking(() -> {
925             List<BookmarkId> observed =
926                     mBookmarkModel.getChildIDs(mBookmarkModel.getDefaultFolder());
927             // Exclude partner bookmarks folder
928             Assert.assertEquals(expected, observed.subList(0, 3));
929             Assert.assertTrue("The selected item should stay selected", test.isItemSelected());
930         });
931     }
932 
933     @Test
934     @MediumTest
testPromoDraggability()935     public void testPromoDraggability() throws Exception {
936         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
937 
938         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
939         openBookmarkManager();
940 
941         ViewHolder promo = mItemsContainer.findViewHolderForAdapterPosition(0);
942 
943         toggleSelectionAndEndAnimation(
944                 testId, (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView);
945 
946         BookmarkItemsAdapter adapter = ((BookmarkItemsAdapter) mItemsContainer.getAdapter());
947         Assert.assertFalse("Promo header should not be passively draggable",
948                 adapter.isPassivelyDraggable(promo));
949         Assert.assertFalse("Promo header should not be actively draggable",
950                 adapter.isActivelyDraggable(promo));
951     }
952 
953     @Test
954     @MediumTest
testPartnerFolderDraggability()955     public void testPartnerFolderDraggability() throws Exception {
956         BookmarkId testId = addFolderWithPartner(TEST_FOLDER_TITLE);
957         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
958         openBookmarkManager();
959 
960         ViewHolder partner = mItemsContainer.findViewHolderForAdapterPosition(2);
961 
962         toggleSelectionAndEndAnimation(
963                 testId, (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView);
964 
965         BookmarkItemsAdapter adapter = ((BookmarkItemsAdapter) mItemsContainer.getAdapter());
966         Assert.assertFalse("Partner bookmarks folder should not be passively draggable",
967                 adapter.isPassivelyDraggable(partner));
968         Assert.assertFalse("Partner bookmarks folder should not be actively draggable",
969                 adapter.isActivelyDraggable(partner));
970     }
971 
972     @Test
973     @MediumTest
testUnselectedItemDraggability()974     public void testUnselectedItemDraggability() throws Exception {
975         BookmarkId aId = addBookmark("a", "http://a.com");
976         addFolder(TEST_FOLDER_TITLE);
977 
978         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
979         openBookmarkManager();
980 
981         ViewHolder test = mItemsContainer.findViewHolderForAdapterPosition(1);
982         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
983                 ((BookmarkFolderRow) test.itemView).getTitle());
984 
985         toggleSelectionAndEndAnimation(
986                 aId, (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(2).itemView);
987 
988         BookmarkItemsAdapter adapter = ((BookmarkItemsAdapter) mItemsContainer.getAdapter());
989         Assert.assertTrue("Unselected rows should be passively draggable",
990                 adapter.isPassivelyDraggable(test));
991         Assert.assertFalse("Unselected rows should not be actively draggable",
992                 adapter.isActivelyDraggable(test));
993     }
994 
995     @Test
996     @MediumTest
testCannotSelectPromo()997     public void testCannotSelectPromo() throws Exception {
998         addFolder(TEST_FOLDER_TITLE);
999 
1000         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1001         openBookmarkManager();
1002 
1003         View promo = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1004         TouchCommon.longPressView(promo);
1005         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1006         Assert.assertFalse("Expected that we would not be in selection mode "
1007                         + "after long pressing on promo view.",
1008                 mManager.getSelectionDelegate().isSelectionEnabled());
1009     }
1010 
1011     @Test
1012     @MediumTest
testCannotSelectPartner()1013     public void testCannotSelectPartner() throws Exception {
1014         addFolderWithPartner(TEST_FOLDER_TITLE);
1015         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1016         openBookmarkManager();
1017 
1018         View partner = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
1019         TouchCommon.longPressView(partner);
1020         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1021         Assert.assertFalse("Expected that we would not be in selection mode "
1022                         + "after long pressing on partner bookmark.",
1023                 mManager.getSelectionDelegate().isSelectionEnabled());
1024     }
1025 
1026     @Test
1027     @SmallTest
1028     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testReadingListItemsInSelectionMode()1029     public void testReadingListItemsInSelectionMode() throws Exception {
1030         addReadingListBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1031         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1032         openBookmarkManager();
1033         TestThreadUtils.runOnUiThreadBlocking(
1034                 () -> mManager.openFolder(mBookmarkModel.getRootFolderId()));
1035         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1036 
1037         // Open the "Reading list" folder.
1038         onView(withText("Reading list")).perform(click());
1039         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1040 
1041         // Select a reading list item. Verify the toolbar menu buttons being shown.
1042         BookmarkRow bookmarkRow =
1043                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1044         TouchCommon.longPressView(bookmarkRow);
1045         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1046         BookmarkActionBar toolbar = mManager.getToolbarForTests();
1047         Assert.assertFalse("Read later items shouldn't have move option",
1048                 toolbar.getMenu().findItem(R.id.selection_mode_move_menu_id).isVisible());
1049         Assert.assertFalse("Read later items shouldn't have edit option",
1050                 toolbar.getMenu().findItem(R.id.selection_mode_edit_menu_id).isVisible());
1051         Assert.assertTrue("Read later items should have delete option",
1052                 toolbar.getMenu().findItem(R.id.selection_mode_delete_menu_id).isVisible());
1053     }
1054 
1055     @Test
1056     @SmallTest
1057     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testReadingListItemMenuItems()1058     public void testReadingListItemMenuItems() throws Exception {
1059         addReadingListBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1060         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1061         openBookmarkManager();
1062         TestThreadUtils.runOnUiThreadBlocking(
1063                 () -> mManager.openFolder(mBookmarkModel.getRootFolderId()));
1064         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1065 
1066         // Open the "Reading list" folder.
1067         onView(withText("Reading list")).perform(click());
1068         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1069 
1070         // Open the three-dot menu and verify the menu options being shown.
1071         View readingListItem = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1072         View more = readingListItem.findViewById(R.id.more);
1073 
1074         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1075         onView(withText("Select")).check(matches(isDisplayed()));
1076         onView(withText("Edit")).check(doesNotExist());
1077         onView(withText("Delete")).check(matches(isDisplayed()));
1078         onView(withText("Mark as read")).check(matches(isDisplayed()));
1079         onView(withText("Move up")).check(doesNotExist());
1080         onView(withText("Move down")).check(doesNotExist());
1081 
1082         // Click "Mark as read". The page should be moved to read section.
1083         onView(withText("Mark as read")).perform(click());
1084         onView(withText("Read")).check(matches(isDisplayed()));
1085 
1086         // A read page should not have "Mark as read" menu option.
1087         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1088         onView(withText("Select")).check(matches(isDisplayed()));
1089         onView(withText("Edit")).check(doesNotExist());
1090         onView(withText("Delete")).check(matches(isDisplayed()));
1091         onView(withText("Mark as read")).check(doesNotExist());
1092         onView(withText("Move up")).check(doesNotExist());
1093         onView(withText("Move down")).check(doesNotExist());
1094     }
1095 
1096     @Test
1097     @MediumTest
testMoveUpMenuItem()1098     public void testMoveUpMenuItem() throws Exception {
1099         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1100         addFolder(TEST_FOLDER_TITLE);
1101         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1102         openBookmarkManager();
1103 
1104         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
1105         Assert.assertEquals("Wrong bookmark item selected.", TEST_PAGE_TITLE_GOOGLE,
1106                 ((BookmarkItemRow) google).getTitle());
1107         View more = google.findViewById(R.id.more);
1108         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1109         onView(withText("Move up")).perform(click());
1110 
1111         // Confirm that the "Google" bookmark is now on top, and that the "test" folder is 2nd
1112         Assert.assertTrue(
1113                 ((BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView)
1114                         .getTitle()
1115                         .equals(TEST_PAGE_TITLE_GOOGLE));
1116         Assert.assertTrue(
1117                 ((BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(2).itemView)
1118                         .getTitle()
1119                         .equals(TEST_FOLDER_TITLE));
1120     }
1121 
1122     @Test
1123     @MediumTest
testMoveDownMenuItem()1124     public void testMoveDownMenuItem() throws Exception {
1125         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1126         addFolder(TEST_FOLDER_TITLE);
1127         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1128         openBookmarkManager();
1129 
1130         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1131         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
1132                 ((BookmarkFolderRow) testFolder).getTitle());
1133         ListMenuButton more = testFolder.findViewById(R.id.more);
1134         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1135         onView(withText("Move down")).perform(click());
1136 
1137         // Confirm that the "Google" bookmark is now on top, and that the "test" folder is 2nd
1138         Assert.assertTrue(
1139                 ((BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView)
1140                         .getTitle()
1141                         .equals(TEST_PAGE_TITLE_GOOGLE));
1142         Assert.assertTrue(
1143                 ((BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(2).itemView)
1144                         .getTitle()
1145                         .equals(TEST_FOLDER_TITLE));
1146     }
1147 
1148     @Test
1149     @MediumTest
testMoveDownGoneForBottomElement()1150     public void testMoveDownGoneForBottomElement() throws Exception {
1151         addBookmarkWithPartner(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1152         addFolderWithPartner(TEST_FOLDER_TITLE);
1153         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1154         openBookmarkManager();
1155 
1156         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
1157         Assert.assertEquals("Wrong bookmark item selected.", TEST_PAGE_TITLE_GOOGLE,
1158                 ((BookmarkItemRow) google).getTitle());
1159         View more = google.findViewById(R.id.more);
1160         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1161         onView(withText("Move down")).check(doesNotExist());
1162     }
1163 
1164     @Test
1165     @MediumTest
testMoveUpGoneForTopElement()1166     public void testMoveUpGoneForTopElement() throws Exception {
1167         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
1168         addFolder(TEST_FOLDER_TITLE);
1169         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1170         openBookmarkManager();
1171 
1172         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1173         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
1174                 ((BookmarkFolderRow) testFolder).getTitle());
1175         ListMenuButton more = testFolder.findViewById(R.id.more);
1176         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1177         onView(withText("Move up")).check(doesNotExist());
1178     }
1179 
1180     @Test
1181     @MediumTest
1182     @DisabledTest(message = "crbug.com/1046653")
testMoveButtonsGoneInSearchMode()1183     public void testMoveButtonsGoneInSearchMode() throws Exception {
1184         addFolder(TEST_FOLDER_TITLE);
1185         openBookmarkManager();
1186 
1187         View searchButton = mManager.getToolbarForTests().findViewById(R.id.search_menu_id);
1188         TestThreadUtils.runOnUiThreadBlocking(searchButton::performClick);
1189 
1190         // Callback occurs when Item "test" is selected.
1191         CriteriaHelper.pollUiThread(
1192                 () -> mManager.getToolbarForTests().isSearching(), "Expected to enter search mode");
1193 
1194         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1195         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
1196                 ((BookmarkFolderRow) testFolder).getTitle());
1197         View more = testFolder.findViewById(R.id.more);
1198         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1199 
1200         onView(withText("Move up")).check(doesNotExist());
1201         onView(withText("Move down")).check(doesNotExist());
1202     }
1203 
1204     @Test
1205     @MediumTest
testMoveButtonsGoneWithOneBookmark()1206     public void testMoveButtonsGoneWithOneBookmark() throws Exception {
1207         addFolder(TEST_FOLDER_TITLE);
1208         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1209         openBookmarkManager();
1210 
1211         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1212         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
1213                 ((BookmarkFolderRow) testFolder).getTitle());
1214         View more = testFolder.findViewById(R.id.more);
1215         TestThreadUtils.runOnUiThreadBlocking(more::callOnClick);
1216 
1217         onView(withText("Move up")).check(doesNotExist());
1218         onView(withText("Move down")).check(doesNotExist());
1219     }
1220 
1221     @Test
1222     @MediumTest
testMoveButtonsGoneForPartnerBookmarks()1223     public void testMoveButtonsGoneForPartnerBookmarks() throws Exception {
1224         loadFakePartnerBookmarkShimForTesting();
1225         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1226         openBookmarkManager();
1227 
1228         // Open partner bookmarks folder.
1229         TestThreadUtils.runOnUiThreadBlocking(
1230                 () -> mManager.openFolder(mBookmarkModel.getPartnerFolderId()));
1231         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1232 
1233         Assert.assertEquals("Wrong number of items in partner bookmark folder.", 2,
1234                 getAdapter().getItemCount());
1235 
1236         // Verify that bookmark 1 is editable (so more button can be triggered) but not movable.
1237         BookmarkId partnerBookmarkId1 = getReorderAdapter().getIdByPosition(0);
1238         TestThreadUtils.runOnUiThreadBlocking(() -> {
1239             BookmarkBridge.BookmarkItem partnerBookmarkItem1 =
1240                     mBookmarkModel.getBookmarkById(partnerBookmarkId1);
1241             partnerBookmarkItem1.forceEditableForTesting();
1242             Assert.assertEquals("Incorrect bookmark type for item 1", BookmarkType.PARTNER,
1243                     partnerBookmarkId1.getType());
1244             Assert.assertFalse(
1245                     "Partner item 1 should not be movable", partnerBookmarkItem1.isMovable());
1246             Assert.assertTrue(
1247                     "Partner item 1 should be editable", partnerBookmarkItem1.isEditable());
1248         });
1249 
1250         // Verify that bookmark 2 is editable (so more button can be triggered) but not movable.
1251         View partnerBookmarkView1 = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1252         View more1 = partnerBookmarkView1.findViewById(R.id.more);
1253         TestThreadUtils.runOnUiThreadBlocking(more1::callOnClick);
1254         onView(withText("Move up")).check(doesNotExist());
1255         onView(withText("Move down")).check(doesNotExist());
1256 
1257         // Verify that bookmark 2 is not movable.
1258         BookmarkId partnerBookmarkId2 = getReorderAdapter().getIdByPosition(1);
1259         TestThreadUtils.runOnUiThreadBlocking(() -> {
1260             BookmarkBridge.BookmarkItem partnerBookmarkItem2 =
1261                     mBookmarkModel.getBookmarkById(partnerBookmarkId2);
1262             partnerBookmarkItem2.forceEditableForTesting();
1263             Assert.assertEquals("Incorrect bookmark type for item 2", BookmarkType.PARTNER,
1264                     partnerBookmarkId2.getType());
1265             Assert.assertFalse(
1266                     "Partner item 2 should not be movable", partnerBookmarkItem2.isMovable());
1267             Assert.assertTrue(
1268                     "Partner item 2 should be editable", partnerBookmarkItem2.isEditable());
1269         });
1270 
1271         // Verify that bookmark 2 does not have move up/down items.
1272         View partnerBookmarkView2 = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1273         View more2 = partnerBookmarkView2.findViewById(R.id.more);
1274         TestThreadUtils.runOnUiThreadBlocking(more2::callOnClick);
1275         onView(withText("Move up")).check(doesNotExist());
1276         onView(withText("Move down")).check(doesNotExist());
1277     }
1278 
1279     @Test
1280     @MediumTest
testTopLevelFolderUpdateAfterSync()1281     public void testTopLevelFolderUpdateAfterSync() throws Exception {
1282         // Set up the test and open the bookmark manager to the Mobile Bookmarks folder.
1283         readPartnerBookmarks();
1284         openBookmarkManager();
1285         BookmarkItemsAdapter adapter = getReorderAdapter();
1286 
1287         // Open the root folder.
1288         TestThreadUtils.runOnUiThreadBlocking(
1289                 () -> mManager.openFolder(mBookmarkModel.getRootFolderId()));
1290 
1291         // Add a bookmark to the Other Bookmarks folder.
1292         TestThreadUtils.runOnUiThreadBlocking(() -> {
1293             mBookmarkModel.addBookmark(
1294                     mBookmarkModel.getOtherFolderId(), 0, TEST_TITLE_A, TEST_URL_A);
1295         });
1296 
1297         TestThreadUtils.runOnUiThreadBlocking(adapter::simulateSignInForTests);
1298         Assert.assertEquals("Expected promo and \"Other Bookmarks\" folder to appear!", 3,
1299                 adapter.getItemCount());
1300     }
1301 
1302     @Test
1303     @MediumTest
testShowInFolder_NoScroll()1304     public void testShowInFolder_NoScroll() throws Exception {
1305         addFolder(TEST_FOLDER_TITLE);
1306         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1307         openBookmarkManager();
1308 
1309         // Enter search mode.
1310         enterSearch();
1311 
1312         // Click "Show in folder".
1313         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1314         clickMoreButtonOnFirstItem(TEST_FOLDER_TITLE);
1315         onView(withText("Show in folder")).perform(click());
1316 
1317         // Assert that the view pulses.
1318         Assert.assertTrue("Expected bookmark row to pulse after clicking \"show in folder\"!",
1319                 checkHighlightPulse(testFolder));
1320 
1321         // Enter search mode again.
1322         enterSearch();
1323 
1324         Assert.assertTrue("Expected bookmark row to not be highlighted "
1325                         + "after entering search mode",
1326                 checkHighlightOff(testFolder));
1327 
1328         // Click "Show in folder" again.
1329         clickMoreButtonOnFirstItem(TEST_FOLDER_TITLE);
1330         onView(withText("Show in folder")).perform(click());
1331         Assert.assertTrue(
1332                 "Expected bookmark row to pulse after clicking \"show in folder\" a 2nd time!",
1333                 checkHighlightPulse(testFolder));
1334     }
1335 
1336     @Test
1337     @MediumTest
testShowInFolder_Scroll()1338     public void testShowInFolder_Scroll() throws Exception {
1339         addFolder(TEST_FOLDER_TITLE); // Index 8
1340         addBookmark(TEST_TITLE_A, TEST_URL_A);
1341         addBookmark(TEST_PAGE_TITLE_FOO, "http://foo.com");
1342         addFolder(TEST_PAGE_TITLE_GOOGLE2);
1343         addFolder("B");
1344         addFolder("C");
1345         addFolder("D");
1346         addFolder("E"); // Index 1
1347         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1348         openBookmarkManager();
1349 
1350         // Enter search mode.
1351         enterSearch();
1352 
1353         TestThreadUtils.runOnUiThreadBlocking(
1354                 () -> mManager.onSearchTextChanged(TEST_FOLDER_TITLE));
1355         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1356 
1357         // This should be the only (& therefore 0-indexed) item.
1358         clickMoreButtonOnFirstItem(TEST_FOLDER_TITLE);
1359 
1360         // Show in folder.
1361         onView(withText("Show in folder")).perform(click());
1362 
1363         // This should be in the 8th position now.
1364         ViewHolder testFolderInList = mItemsContainer.findViewHolderForAdapterPosition(8);
1365         Assert.assertFalse(
1366                 "Expected list to scroll bookmark item into view", testFolderInList == null);
1367         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
1368                 ((BookmarkFolderRow) testFolderInList.itemView).getTitle());
1369         Assert.assertTrue("Expected highlight to pulse on after scrolling to the item!",
1370                 checkHighlightPulse(testFolderInList.itemView));
1371     }
1372 
1373     @Test
1374     @MediumTest
testShowInFolder_OpenOtherFolder()1375     public void testShowInFolder_OpenOtherFolder() throws Exception {
1376         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
1377         TestThreadUtils.runOnUiThreadBlocking(
1378                 () -> mBookmarkModel.addBookmark(testId, 0, TEST_TITLE_A, TEST_URL_A));
1379         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_SYNC);
1380         openBookmarkManager();
1381 
1382         // Enter search mode.
1383         enterSearch();
1384         TestThreadUtils.runOnUiThreadBlocking(() -> mManager.onSearchTextChanged(TEST_URL_A));
1385         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1386 
1387         // This should be the only (& therefore 0-indexed) item.
1388         clickMoreButtonOnFirstItem(TEST_TITLE_A);
1389 
1390         // Show in folder.
1391         onView(withText("Show in folder")).perform(click());
1392         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1393 
1394         // Make sure that we're in the right folder (index 1 because of promo).
1395         View itemA = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1396         Assert.assertEquals("Wrong bookmark item selected.", TEST_TITLE_A,
1397                 ((BookmarkItemRow) itemA).getTitle());
1398 
1399         Assert.assertTrue("Expected highlight to pulse after opening an item in another folder!",
1400                 checkHighlightPulse(itemA));
1401 
1402         // Open mobile bookmarks folder, then go back to the subfolder.
1403         TestThreadUtils.runOnUiThreadBlocking(() -> {
1404             mManager.openFolder(mBookmarkModel.getMobileFolderId());
1405             mManager.openFolder(testId);
1406         });
1407         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1408 
1409         View itemASecondView = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1410         Assert.assertEquals("Wrong bookmark item selected.", TEST_TITLE_A,
1411                 ((BookmarkItemRow) itemASecondView).getTitle());
1412         Assert.assertTrue(
1413                 "Expected highlight to not be highlighted after exiting and re-entering folder!",
1414                 checkHighlightOff(itemASecondView));
1415     }
1416 
1417     @Test
1418     @SmallTest
testAddBookmarkInBackgroundWithSelection()1419     public void testAddBookmarkInBackgroundWithSelection() throws Exception {
1420         BookmarkId folder = addFolder(TEST_FOLDER_TITLE);
1421         BookmarkId id = addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, folder);
1422         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1423         openBookmarkManager();
1424 
1425         // Open the new folder where these bookmarks were created.
1426         openFolder(folder);
1427 
1428         Assert.assertEquals(1, getAdapter().getItemCount());
1429         BookmarkRow row =
1430                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1431         toggleSelectionAndEndAnimation(id, row);
1432         CallbackHelper helper = new CallbackHelper();
1433         getAdapter().registerAdapterDataObserver(new AdapterDataObserver() {
1434             @Override
1435             public void onChanged() {
1436                 helper.notifyCalled();
1437             }
1438         });
1439 
1440         TestThreadUtils.runOnUiThreadBlocking(() -> {
1441             mBookmarkModel.addBookmark(folder, 1, TEST_PAGE_TITLE_GOOGLE, mTestPage);
1442         });
1443 
1444         helper.waitForCallback(0, 1);
1445         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1446         TestThreadUtils.runOnUiThreadBlocking(() -> {
1447             Assert.assertTrue(isItemPresentInBookmarkList(TEST_PAGE_TITLE_FOO));
1448             Assert.assertTrue(isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
1449             Assert.assertEquals(2, getAdapter().getItemCount());
1450             Assert.assertTrue("The selected row should be kept selected", row.isItemSelected());
1451         });
1452     }
1453 
1454     @Test
1455     @SmallTest
testDeleteAllSelectedBookmarksInBackground()1456     public void testDeleteAllSelectedBookmarksInBackground() throws Exception {
1457         // Select one bookmark and then remove that in background.
1458         // In the meantime, the toolbar changes from selection mode to normal mode.
1459         BookmarkId folder = addFolder(TEST_FOLDER_TITLE);
1460         BookmarkId fooId = addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, folder);
1461         BookmarkId googleId = addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage, folder);
1462         BookmarkId aId = addBookmark(TEST_TITLE_A, TEST_URL_A, folder);
1463         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1464         openBookmarkManager();
1465 
1466         // Open the new folder where these bookmarks were created.
1467         openFolder(folder);
1468 
1469         Assert.assertEquals(3, getAdapter().getItemCount());
1470         BookmarkRow row =
1471                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1472         toggleSelectionAndEndAnimation(googleId, row);
1473         CallbackHelper helper = new CallbackHelper();
1474         mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); });
1475 
1476         removeBookmark(googleId);
1477 
1478         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1479         helper.waitForCallback(0, 1);
1480         TestThreadUtils.runOnUiThreadBlocking(() -> {
1481             Assert.assertFalse(
1482                     "Item is not deleted", isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
1483             Assert.assertEquals(2, getReorderAdapter().getItemCount());
1484             Assert.assertEquals("Bookmark View should be back to normal view",
1485                     mManager.getToolbarForTests().getCurrentViewType(), ViewType.NORMAL_VIEW);
1486         });
1487     }
1488 
1489     @Test
1490     @SmallTest
testDeleteSomeSelectedBookmarksInBackground()1491     public void testDeleteSomeSelectedBookmarksInBackground() throws Exception {
1492         // selected on bookmarks and then remove one of them in background
1493         // in the meantime, the toolbar stays in selection mode
1494         BookmarkId folder = addFolder(TEST_FOLDER_TITLE);
1495         BookmarkId fooId = addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, folder);
1496         BookmarkId googleId = addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage, folder);
1497         BookmarkId aId = addBookmark(TEST_TITLE_A, TEST_URL_A, folder);
1498         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1499         openBookmarkManager();
1500 
1501         // Open the new folder where these bookmarks were created.
1502         openFolder(folder);
1503 
1504         Assert.assertEquals(3, getAdapter().getItemCount());
1505         BookmarkRow row =
1506                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
1507         toggleSelectionAndEndAnimation(googleId, row);
1508         BookmarkRow aRow =
1509                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1510         toggleSelectionAndEndAnimation(aId, aRow);
1511         CallbackHelper helper = new CallbackHelper();
1512         mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); });
1513 
1514         removeBookmark(googleId);
1515 
1516         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1517         helper.waitForCallback(0, 1);
1518         TestThreadUtils.runOnUiThreadBlocking(() -> {
1519             Assert.assertFalse(
1520                     "Item is not deleted", isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
1521             Assert.assertEquals(2, getReorderAdapter().getItemCount());
1522             Assert.assertTrue("Item selected should not be cleared", aRow.isItemSelected());
1523             Assert.assertEquals("Should stay in selection mode because there is one selected",
1524                     mManager.getToolbarForTests().getCurrentViewType(), ViewType.SELECTION_VIEW);
1525         });
1526     }
1527 
1528     @Test
1529     @SmallTest
testUpdateSelectedBookmarkInBackground()1530     public void testUpdateSelectedBookmarkInBackground() throws Exception {
1531         BookmarkId folder = addFolder(TEST_FOLDER_TITLE);
1532         BookmarkId id = addBookmark(TEST_PAGE_TITLE_FOO, mTestPageFoo, folder);
1533         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1534         openBookmarkManager();
1535 
1536         // Open the new folder where these bookmarks were created.
1537         openFolder(folder);
1538 
1539         Assert.assertEquals(1, getAdapter().getItemCount());
1540         BookmarkRow row =
1541                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1542         toggleSelectionAndEndAnimation(id, row);
1543         CallbackHelper helper = new CallbackHelper();
1544         TestThreadUtils.runOnUiThreadBlocking(
1545                 () -> mBookmarkModel.addObserver(new BookmarkModelObserver() {
1546                     @Override
1547                     public void bookmarkModelChanged() {
1548                         helper.notifyCalled();
1549                     }
1550                 }));
1551 
1552         TestThreadUtils.runOnUiThreadBlocking(
1553                 () -> mBookmarkModel.setBookmarkTitle(id, TEST_PAGE_TITLE_GOOGLE));
1554 
1555         helper.waitForCallback(0, 1);
1556         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1557         TestThreadUtils.runOnUiThreadBlocking(() -> {
1558             Assert.assertFalse(isItemPresentInBookmarkList(TEST_PAGE_TITLE_FOO));
1559             Assert.assertTrue(isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
1560             Assert.assertEquals(1, getAdapter().getItemCount());
1561             Assert.assertTrue("The selected row should stay selected", row.isItemSelected());
1562         });
1563     }
1564 
1565     /**
1566      * Verifies the top level elements with the reading list folder.
1567      * Layout:
1568      *  - Reading list folder.
1569      *  - Divider
1570      *  - Mobile bookmark folder.
1571      */
1572     @Test
1573     @SmallTest
1574     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testReadingListFolderShown()1575     public void testReadingListFolderShown() throws Exception {
1576         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1577         openBookmarkManager();
1578         TestThreadUtils.runOnUiThreadBlocking(
1579                 () -> mManager.openFolder(mBookmarkModel.getRootFolderId()));
1580         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1581         Assert.assertEquals("Wrong number of top level elements.", 3, getAdapter().getItemCount());
1582 
1583         // Reading list should show in the root folder.
1584         View readingListRow = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1585         Assert.assertEquals("No overflow menu for reading list folder.", View.GONE,
1586                 readingListRow.findViewById(R.id.more).getVisibility());
1587         Assert.assertEquals("The 1st view should be reading list.", BookmarkType.READING_LIST,
1588                 getIdByPosition(0).getType());
1589         onView(withText("Reading list")).check(matches(isDisplayed()));
1590 
1591         Assert.assertEquals("The 2nd view should be a divider.", BookmarkListEntry.ViewType.DIVIDER,
1592                 getAdapter().getItemViewType(1));
1593         Assert.assertEquals("The 3rd view should be a normal folder.",
1594                 BookmarkListEntry.ViewType.FOLDER, getAdapter().getItemViewType(2));
1595     }
1596 
1597     @Test
1598     @SmallTest
1599     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testReadingListFolderShownOneUnreadPage()1600     public void testReadingListFolderShownOneUnreadPage() throws Exception {
1601         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1602         openBookmarkManager();
1603         TestThreadUtils.runOnUiThreadBlocking(() -> {
1604             mBookmarkModel.addToReadingList("a", "https://a.com/reading_list_0");
1605             mManager.openFolder(mBookmarkModel.getRootFolderId());
1606         });
1607         onView(withText("Reading list")).check(matches(isDisplayed()));
1608         onView(withText("1 unread page")).check(matches(isDisplayed()));
1609     }
1610 
1611     @Test
1612     @SmallTest
1613     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testReadingListFolderShownMultipleUnreadPages()1614     public void testReadingListFolderShownMultipleUnreadPages() throws Exception {
1615         BookmarkPromoHeader.forcePromoStateForTests(PromoState.PROMO_NONE);
1616         openBookmarkManager();
1617         TestThreadUtils.runOnUiThreadBlocking(() -> {
1618             mBookmarkModel.addToReadingList("a", "https://a.com/reading_list_0");
1619             mBookmarkModel.addToReadingList("b", "https://a.com/reading_list_1");
1620             mManager.openFolder(mBookmarkModel.getRootFolderId());
1621         });
1622         onView(withText("Reading list")).check(matches(isDisplayed()));
1623         onView(withText("2 unread pages")).check(matches(isDisplayed()));
1624     }
1625 
1626     @Test
1627     @MediumTest
1628     @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
testAddReadingListItemFromBottomSheet()1629     public void testAddReadingListItemFromBottomSheet() throws Exception {
1630         mActivityTestRule.loadUrl(mTestPage);
1631         TestThreadUtils.runOnUiThreadBlocking(
1632                 () -> mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting());
1633         BookmarkTestUtil.waitForBookmarkModelLoaded();
1634 
1635         // Click the star icon to trigger bookmark bottom sheet.
1636         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
1637                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
1638 
1639         // Click the reading list folder in the bottom sheet, and wait for reading list item added.
1640         onView(withText("Reading list")).check(matches(isDisplayed())).perform(click());
1641         CriteriaHelper.pollUiThread(() -> mBookmarkModel.getReadingListItem(mTestPage) != null);
1642 
1643         TestThreadUtils.runOnUiThreadBlocking(() -> {
1644             // Check reading list item states.
1645             BookmarkItem readingListItem = mBookmarkModel.getReadingListItem(mTestPage);
1646             Assert.assertEquals(mTestPage, readingListItem.getUrl());
1647             Assert.assertEquals(TEST_PAGE_TITLE_GOOGLE, readingListItem.getTitle());
1648             Assert.assertFalse(readingListItem.isRead());
1649 
1650             // Snackbar has been shown.
1651             SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
1652             Snackbar currentSnackbar = snackbarManager.getCurrentSnackbarForTesting();
1653             Assert.assertEquals("Add to reading list snackbar not shown.",
1654                     Snackbar.UMA_READING_LIST_BOOKMARK_ADDED,
1655                     currentSnackbar.getIdentifierForTesting());
1656         });
1657 
1658         waitForOfflinePageSaved(mTestPage);
1659 
1660         // Click the star button again to launch the edit activity.
1661         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
1662                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
1663         waitForEditActivity();
1664     }
1665 
1666     @Test
1667     @MediumTest
1668     @EnableFeatures({ChromeFeatureList.READ_LATER,
1669             ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR + "<Study"})
1670     @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
1671             "force-fieldtrial-params=Study.Group:three_button_action_bar/add_to_option"})
1672     public void
testAddReadingListItemFromAddToOption()1673     testAddReadingListItemFromAddToOption() throws Exception {
1674         mActivityTestRule.loadUrl(mTestPage);
1675         TestThreadUtils.runOnUiThreadBlocking(
1676                 () -> mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting());
1677         BookmarkTestUtil.waitForBookmarkModelLoaded();
1678 
1679         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
1680                 mActivityTestRule.getActivity(), R.id.add_to_reading_list_menu_id);
1681         CriteriaHelper.pollUiThread(() -> mBookmarkModel.getReadingListItem(mTestPage) != null);
1682         TestThreadUtils.runOnUiThreadBlocking(() -> {
1683             // Check reading list item states.
1684             BookmarkItem readingListItem = mBookmarkModel.getReadingListItem(mTestPage);
1685             Assert.assertEquals(mTestPage, readingListItem.getUrl());
1686             Assert.assertEquals(TEST_PAGE_TITLE_GOOGLE, readingListItem.getTitle());
1687             Assert.assertFalse(readingListItem.isRead());
1688 
1689             // Reading list snackbar has shown.
1690             SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
1691             Snackbar currentSnackbar = snackbarManager.getCurrentSnackbarForTesting();
1692             Assert.assertEquals("Add to reading list snackbar not shown.",
1693                     Snackbar.UMA_READING_LIST_BOOKMARK_ADDED,
1694                     currentSnackbar.getIdentifierForTesting());
1695         });
1696     }
1697 
1698 
1699     /**
1700      * Adds a bookmark in the scenario where we have partner bookmarks.
1701      *
1702      * @param title The title of the bookmark to add.
1703      * @param url The url of the bookmark to add.
1704      * @return The BookmarkId of the added bookmark.
1705      * @throws ExecutionException If something goes wrong while we are trying to add the bookmark.
1706      */
addBookmarkWithPartner(String title, String url)1707     private BookmarkId addBookmarkWithPartner(String title, String url) throws ExecutionException {
1708         loadEmptyPartnerBookmarksForTesting();
1709         return TestThreadUtils.runOnUiThreadBlocking(
1710                 () -> mBookmarkModel.addBookmark(mBookmarkModel.getDefaultFolder(), 0, title, url));
1711     }
1712 
1713     /**
1714      * Adds a folder in the scenario where we have partner bookmarks.
1715      *
1716      * @param title The title of the folder to add.
1717      * @return The BookmarkId of the added folder.
1718      * @throws ExecutionException If something goes wrong while we are trying to add the bookmark.
1719      */
addFolderWithPartner(String title)1720     private BookmarkId addFolderWithPartner(String title) throws ExecutionException {
1721         loadEmptyPartnerBookmarksForTesting();
1722         return TestThreadUtils.runOnUiThreadBlocking(
1723                 () -> mBookmarkModel.addFolder(mBookmarkModel.getDefaultFolder(), 0, title));
1724     }
1725 
getReorderAdapter()1726     private BookmarkItemsAdapter getReorderAdapter() {
1727         return (BookmarkItemsAdapter) getAdapter();
1728     }
1729 
enterSearch()1730     private void enterSearch() throws Exception {
1731         View searchButton = mManager.getToolbarForTests().findViewById(R.id.search_menu_id);
1732         TestThreadUtils.runOnUiThreadBlocking(searchButton::performClick);
1733         CriteriaHelper.pollUiThread(
1734                 () -> mManager.getToolbarForTests().isSearching(), "Expected to enter search mode");
1735     }
1736 
clickMoreButtonOnFirstItem(String expectedBookmarkItemTitle)1737     private void clickMoreButtonOnFirstItem(String expectedBookmarkItemTitle) throws Exception {
1738         View firstItem = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
1739         Assert.assertEquals("Wrong bookmark item selected.", expectedBookmarkItemTitle,
1740                 firstItem instanceof BookmarkItemRow ? ((BookmarkItemRow) firstItem).getTitle()
1741                                                      : ((BookmarkFolderRow) firstItem).getTitle());
1742         View more = firstItem.findViewById(R.id.more);
1743         TestThreadUtils.runOnUiThreadBlocking(more::performClick);
1744     }
1745 
1746     /**
1747      * Returns the View that has the given text.
1748      *
1749      * @param viewGroup    The group to which the view belongs.
1750      * @param expectedText The expected description text.
1751      * @return The unique view, if one exists. Throws an exception if one doesn't exist.
1752      */
getViewWithText(final ViewGroup viewGroup, final String expectedText)1753     private static View getViewWithText(final ViewGroup viewGroup, final String expectedText) {
1754         return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<View>() {
1755             @Override
1756             public View call() {
1757                 ArrayList<View> outViews = new ArrayList<>();
1758                 ArrayList<View> matchingViews = new ArrayList<>();
1759                 viewGroup.findViewsWithText(outViews, expectedText, View.FIND_VIEWS_WITH_TEXT);
1760                 // outViews includes all views whose text contains expectedText as a
1761                 // case-insensitive substring. Filter these views to find only exact string matches.
1762                 for (View v : outViews) {
1763                     if (TextUtils.equals(((TextView) v).getText().toString(), expectedText)) {
1764                         matchingViews.add(v);
1765                     }
1766                 }
1767                 Assert.assertEquals("Exactly one item should be present.", 1, matchingViews.size());
1768                 return matchingViews.get(0);
1769             }
1770         });
1771     }
1772 
1773     private void toggleSelectionAndEndAnimation(BookmarkId id, BookmarkRow view) {
1774         TestThreadUtils.runOnUiThreadBlocking(() -> {
1775             mManager.getSelectionDelegate().toggleSelectionForItem(id);
1776             view.endAnimationsForTests();
1777             mManager.getToolbarForTests().endAnimationsForTesting();
1778         });
1779         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1780     }
1781 
1782     private BookmarkId addBookmark(final String title, final String url, BookmarkId parent)
1783             throws ExecutionException {
1784         readPartnerBookmarks();
1785         return TestThreadUtils.runOnUiThreadBlocking(
1786                 () -> mBookmarkModel.addBookmark(parent, 0, title, url));
1787     }
1788 
1789     private BookmarkId addBookmark(final String title, final String url) throws ExecutionException {
1790         readPartnerBookmarks();
1791         return TestThreadUtils.runOnUiThreadBlocking(
1792                 () -> mBookmarkModel.addBookmark(mBookmarkModel.getDefaultFolder(), 0, title, url));
1793     }
1794 
1795     private BookmarkId addReadingListBookmark(final String title, final String url)
1796             throws ExecutionException {
1797         readPartnerBookmarks();
1798         return TestThreadUtils.runOnUiThreadBlocking(
1799                 () -> mBookmarkModel.addToReadingList(title, url));
1800     }
1801 
1802     private BookmarkId addFolder(final String title) throws ExecutionException {
1803         readPartnerBookmarks();
1804         return TestThreadUtils.runOnUiThreadBlocking(
1805                 () -> mBookmarkModel.addFolder(mBookmarkModel.getDefaultFolder(), 0, title));
1806     }
1807 
1808     private BookmarkId addFolder(final String title, BookmarkId parent) throws ExecutionException {
1809         readPartnerBookmarks();
1810         return TestThreadUtils.runOnUiThreadBlocking(
1811                 () -> mBookmarkModel.addFolder(parent, 0, title));
1812     }
1813 
1814     private void removeBookmark(final BookmarkId bookmarkId) {
1815         TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.deleteBookmark(bookmarkId));
1816     }
1817 
1818     private RecyclerView.Adapter getAdapter() {
1819         return mItemsContainer.getAdapter();
1820     }
1821 
1822     private BookmarkManager getBookmarkManager() {
1823         return (BookmarkManager) getReorderAdapter().getDelegateForTesting();
1824     }
1825 
1826     private BookmarkId getIdByPosition(int pos) {
1827         return getReorderAdapter().getIdByPosition(pos);
1828     }
1829 
1830     private void searchBookmarks(final String query) {
1831         TestThreadUtils.runOnUiThreadBlocking(() -> getReorderAdapter().search(query));
1832     }
1833 
1834     private void openFolder(BookmarkId folder) {
1835         final BookmarkDelegate delegate = getBookmarkManager();
1836         TestThreadUtils.runOnUiThreadBlocking(() -> delegate.openFolder(folder));
1837         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
1838     }
1839 
1840     private void waitForEditActivity() {
1841         CriteriaHelper.pollUiThread(() -> {
1842             Criteria.checkThat(ApplicationStatus.getLastTrackedFocusedActivity(),
1843                     IsInstanceOf.instanceOf(BookmarkEditActivity.class));
1844         });
1845         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
1846     }
1847 
1848     private ChromeTabbedActivity waitForTabbedActivity() {
1849         CriteriaHelper.pollUiThread(() -> {
1850             Criteria.checkThat(ApplicationStatus.getLastTrackedFocusedActivity(),
1851                     IsInstanceOf.instanceOf(ChromeTabbedActivity.class));
1852         });
1853         return (ChromeTabbedActivity) ApplicationStatus.getLastTrackedFocusedActivity();
1854     }
1855 }
1856