1 // Copyright 2020 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 android.app.Activity;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.support.test.InstrumentationRegistry;
11 import android.view.MenuItem;
12 
13 import androidx.test.filters.MediumTest;
14 
15 import org.junit.After;
16 import org.junit.Assert;
17 import org.junit.Before;
18 import org.junit.Rule;
19 import org.junit.Test;
20 import org.junit.runner.RunWith;
21 import org.mockito.Mockito;
22 
23 import org.chromium.base.ActivityState;
24 import org.chromium.base.ApplicationStatus;
25 import org.chromium.base.ApplicationStatus.ActivityStateListener;
26 import org.chromium.base.test.BaseJUnit4ClassRunner;
27 import org.chromium.base.test.util.Batch;
28 import org.chromium.base.test.util.CallbackHelper;
29 import org.chromium.base.test.util.CriteriaHelper;
30 import org.chromium.base.test.util.Feature;
31 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
32 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
33 import org.chromium.chrome.browser.profiles.Profile;
34 import org.chromium.chrome.test.ChromeBrowserTestRule;
35 import org.chromium.chrome.test.util.BookmarkTestUtil;
36 import org.chromium.components.bookmarks.BookmarkId;
37 import org.chromium.content_public.browser.test.util.TestThreadUtils;
38 
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.TimeoutException;
41 
42 /**
43  * Tests functionality in BookmarkEditActivity.
44  */
45 @RunWith(BaseJUnit4ClassRunner.class)
46 @Batch(Batch.PER_CLASS)
47 public class BookmarkEditTest {
48     private static final String TITLE_A = "a";
49     private static final String TITLE_B = "b";
50     private static final String URL_A = "http://a.com/";
51     private static final String URL_B = "http://b.com/";
52     private static final String FOLDER_A = "FolderA";
53 
54     @Rule
55     public final ChromeBrowserTestRule mChromeBrowserTestRule = new ChromeBrowserTestRule();
56 
57     private BookmarkModel mBookmarkModel;
58     private BookmarkModel.BookmarkModelObserver mModelObserver;
59     private CallbackHelper mModelChangedCallback = new CallbackHelper();
60     private BookmarkId mBookmarkId;
61     private BookmarkId mMobileNode;
62     private BookmarkId mOtherNode;
63     private BookmarkEditActivity mBookmarkEditActivity;
64 
65     private CallbackHelper mDestroyedCallback = new CallbackHelper();
66     private ActivityStateListener mActivityStateListener = new ActivityStateListener() {
67         @Override
68         public void onActivityStateChange(Activity activity, int newState) {
69             if (newState == ActivityState.DESTROYED) mDestroyedCallback.notifyCalled();
70         }
71     };
72 
73     @Before
setUp()74     public void setUp() throws TimeoutException {
75         TestThreadUtils.runOnUiThreadBlocking(() -> {
76             Profile profile = Profile.getLastUsedRegularProfile();
77             mBookmarkModel = new BookmarkModel(profile);
78             mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting();
79         });
80 
81         BookmarkTestUtil.waitForBookmarkModelLoaded();
82         TestThreadUtils.runOnUiThreadBlocking(() -> {
83             mMobileNode = mBookmarkModel.getMobileFolderId();
84             mOtherNode = mBookmarkModel.getOtherFolderId();
85         });
86         mBookmarkId = BookmarkModelTest.addBookmark(mBookmarkModel, mMobileNode, 0, TITLE_A, URL_A);
87 
88         mModelObserver = new BookmarkModelObserver() {
89             @Override
90             public void bookmarkModelChanged() {
91                 mModelChangedCallback.notifyCalled();
92             }
93         };
94         mBookmarkModel.addObserver(mModelObserver);
95 
96         startEditActivity(mBookmarkId);
97         ApplicationStatus.registerStateListenerForActivity(
98                 mActivityStateListener, mBookmarkEditActivity);
99     }
100 
101     @After
tearDown()102     public void tearDown() {
103         mBookmarkModel.removeObserver(mModelObserver);
104         TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.removeAllUserBookmarks());
105         ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
106     }
107 
108     @Test
109     @MediumTest
110     @Feature({"Bookmark"})
testEditTitleAndUrl()111     public void testEditTitleAndUrl() throws ExecutionException, TimeoutException {
112         Assert.assertEquals("Incorrect title.", TITLE_A,
113                 mBookmarkEditActivity.getTitleEditText().getEditText().getText().toString());
114         Assert.assertEquals("Incorrect url.", URL_A,
115                 mBookmarkEditActivity.getUrlEditText().getEditText().getText().toString());
116 
117         int currentModelChangedCount = mModelChangedCallback.getCallCount();
118         TestThreadUtils.runOnUiThreadBlocking(() -> {
119             mBookmarkEditActivity.getTitleEditText().getEditText().setText(TITLE_B);
120             mBookmarkEditActivity.getUrlEditText().getEditText().setText(URL_B);
121             mBookmarkEditActivity.finish();
122         });
123         mDestroyedCallback.waitForCallback(0);
124 
125         BookmarkItem bookmarkItem = getBookmarkItem(mBookmarkId);
126         Assert.assertEquals("Incorrect title after edit.", TITLE_B, bookmarkItem.getTitle());
127         Assert.assertEquals("Incorrect url after edit.", URL_B, bookmarkItem.getUrl());
128     }
129 
130     @Test
131     @MediumTest
132     @Feature({"Bookmark"})
testEditEmptyInputRejected()133     public void testEditEmptyInputRejected() throws ExecutionException, TimeoutException {
134         Assert.assertEquals("Incorrect title.", TITLE_A,
135                 mBookmarkEditActivity.getTitleEditText().getEditText().getText().toString());
136         Assert.assertEquals("Incorrect url.", URL_A,
137                 mBookmarkEditActivity.getUrlEditText().getEditText().getText().toString());
138 
139         TestThreadUtils.runOnUiThreadBlocking(() -> {
140             mBookmarkEditActivity.getTitleEditText().getEditText().setText("");
141             mBookmarkEditActivity.getUrlEditText().getEditText().setText("");
142             mBookmarkEditActivity.finish();
143         });
144         mDestroyedCallback.waitForCallback(0);
145 
146         BookmarkItem bookmarkItem = getBookmarkItem(mBookmarkId);
147         Assert.assertEquals("Incorrect title after edit.", TITLE_A, bookmarkItem.getTitle());
148         Assert.assertEquals("Incorrect url after edit.", URL_A, bookmarkItem.getUrl());
149     }
150 
151     @Test
152     @MediumTest
153     @Feature({"Bookmark"})
testInvalidUrlRejected()154     public void testInvalidUrlRejected() throws ExecutionException, TimeoutException {
155         Assert.assertEquals("Incorrect url.", URL_A,
156                 mBookmarkEditActivity.getUrlEditText().getEditText().getText().toString());
157 
158         TestThreadUtils.runOnUiThreadBlocking(() -> {
159             mBookmarkEditActivity.getUrlEditText().getEditText().setText("http:://?foo=bar");
160             mBookmarkEditActivity.finish();
161         });
162         mDestroyedCallback.waitForCallback(0);
163 
164         BookmarkItem bookmarkItem = getBookmarkItem(mBookmarkId);
165         Assert.assertEquals("Incorrect url after edit.", URL_A, bookmarkItem.getUrl());
166     }
167 
168     @Test
169     @MediumTest
170     @Feature({"Bookmark"})
testEditActivityDeleteButton()171     public void testEditActivityDeleteButton() throws ExecutionException, TimeoutException {
172         TestThreadUtils.runOnUiThreadBlocking(() -> {
173             mBookmarkEditActivity.onOptionsItemSelected(mBookmarkEditActivity.getDeleteButton());
174         });
175         mDestroyedCallback.waitForCallback(0);
176 
177         BookmarkItem bookmarkItem = getBookmarkItem(mBookmarkId);
178         Assert.assertNull("Bookmark item should have been deleted.", bookmarkItem);
179     }
180 
181     @Test
182     @MediumTest
183     @Feature({"Bookmark"})
testEditActivityHomeButton()184     public void testEditActivityHomeButton() throws ExecutionException {
185         MenuItem item = Mockito.mock(MenuItem.class);
186         Mockito.when(item.getItemId()).thenReturn(android.R.id.home);
187         TestThreadUtils.runOnUiThreadBlocking(
188                 () -> mBookmarkEditActivity.onOptionsItemSelected(item));
189 
190         Assert.assertTrue("BookmarkActivity should be finishing or destroyed.",
191                 mBookmarkEditActivity.isFinishing() || mBookmarkEditActivity.isDestroyed());
192     }
193 
194     @Test
195     @MediumTest
196     @Feature({"Bookmark"})
testEditActivityReflectsModelChanges()197     public void testEditActivityReflectsModelChanges() throws TimeoutException, ExecutionException {
198         Assert.assertEquals("Incorrect title.", TITLE_A,
199                 mBookmarkEditActivity.getTitleEditText().getEditText().getText().toString());
200         Assert.assertEquals("Incorrect folder.", getBookmarkItem(mMobileNode).getTitle(),
201                 mBookmarkEditActivity.getFolderTextView().getText());
202 
203         int currentModelChangedCount = mModelChangedCallback.getCallCount();
204         TestThreadUtils.runOnUiThreadBlocking(() -> {
205             mBookmarkModel.setBookmarkTitle(mBookmarkId, TITLE_B);
206             mBookmarkModel.moveBookmark(mBookmarkId, mOtherNode, 0);
207         });
208         mModelChangedCallback.waitForCallback(currentModelChangedCount);
209 
210         Assert.assertEquals("Title shouldn't change after model update.", TITLE_A,
211                 mBookmarkEditActivity.getTitleEditText().getEditText().getText().toString());
212         Assert.assertEquals("Folder should change after model update.",
213                 getBookmarkItem(mOtherNode).getTitle(),
214                 mBookmarkEditActivity.getFolderTextView().getText());
215     }
216 
217     @Test
218     @MediumTest
219     @Feature({"Bookmark"})
testEditActivityFinishesWhenBookmarkDeleted()220     public void testEditActivityFinishesWhenBookmarkDeleted() throws TimeoutException {
221         int currentModelChangedCount = mModelChangedCallback.getCallCount();
222         TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.deleteBookmark(mBookmarkId));
223         mModelChangedCallback.waitForCallback(currentModelChangedCount);
224 
225         Assert.assertTrue("BookmarkActivity should be finishing or destroyed.",
226                 mBookmarkEditActivity.isFinishing() || mBookmarkEditActivity.isDestroyed());
227     }
228 
229     @Test
230     @MediumTest
231     @Feature({"Bookmark"})
testEditFolderLocation()232     public void testEditFolderLocation() throws ExecutionException, TimeoutException {
233         BookmarkId testFolder = addFolder(mMobileNode, 0, FOLDER_A);
234         TestThreadUtils.runOnUiThreadBlocking(
235                 () -> mBookmarkEditActivity.getFolderTextView().performClick());
236         waitForMoveFolderActivity();
237 
238         TestThreadUtils.runOnUiThreadBlocking(() -> {
239             BookmarkFolderSelectActivity folderSelectActivity =
240                     (BookmarkFolderSelectActivity)
241                             ApplicationStatus.getLastTrackedFocusedActivity();
242             int pos = folderSelectActivity.getFolderPositionForTesting(testFolder);
243             Assert.assertNotEquals("Didn't find position for test folder.", -1, pos);
244             folderSelectActivity.performClickForTesting(pos);
245         });
246 
247         waitForEditActivity();
248         Assert.assertEquals("Folder should change after folder activity finishes.", FOLDER_A,
249                 mBookmarkEditActivity.getFolderTextView().getText());
250     }
251 
getBookmarkItem(BookmarkId bookmarkId)252     private BookmarkItem getBookmarkItem(BookmarkId bookmarkId) throws ExecutionException {
253         return TestThreadUtils.runOnUiThreadBlocking(
254                 () -> mBookmarkModel.getBookmarkById(bookmarkId));
255     }
256 
startEditActivity(BookmarkId bookmarkId)257     private void startEditActivity(BookmarkId bookmarkId) {
258         Context context = InstrumentationRegistry.getTargetContext();
259         Intent intent = new Intent(context, BookmarkEditActivity.class);
260         intent.putExtra(BookmarkEditActivity.INTENT_BOOKMARK_ID, bookmarkId.toString());
261         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
262         mBookmarkEditActivity = (BookmarkEditActivity) InstrumentationRegistry.getInstrumentation()
263                                         .startActivitySync(intent);
264     }
265 
addFolder(BookmarkId parent, int index, String title)266     private BookmarkId addFolder(BookmarkId parent, int index, String title)
267             throws ExecutionException {
268         return TestThreadUtils.runOnUiThreadBlocking(
269                 () -> mBookmarkModel.addFolder(parent, index, title));
270     }
271 
waitForMoveFolderActivity()272     private void waitForMoveFolderActivity() {
273         // clang-format off
274         CriteriaHelper.pollUiThread(()->
275                 ApplicationStatus.getLastTrackedFocusedActivity()
276                     instanceof BookmarkFolderSelectActivity,
277                 "Timed out waiting for BookmarkFolderSelectActivity");
278         // clang-format on
279         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
280     }
281 
waitForEditActivity()282     private void waitForEditActivity() {
283         // clang-format off
284         CriteriaHelper.pollUiThread(()->
285                 ApplicationStatus.getLastTrackedFocusedActivity() instanceof BookmarkEditActivity,
286                 "Timed out waiting for BookmarkEditActivity");
287         // clang-format on
288         mBookmarkEditActivity =
289                 (BookmarkEditActivity) ApplicationStatus.getLastTrackedFocusedActivity();
290         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
291     }
292 }
293