Skip to content

Commit

Permalink
Allow selecting multiple notebooks
Browse files Browse the repository at this point in the history
In this solution, there are some differences in the top "app bar" menu
choices when one vs many notebooks are selected:

- The "export" and "rename" choices are removed when multiple notebooks
  are selected.
- When setting the repository link of multiple notebooks, no repository
  is pre-selected, even if all notebooks are currently linked to the
  same repository. The rationale is that I found no good way of showing
  existing links to multiple repos, so it seemed better to me to never
  make a pre-selection.
- When deleting a single notebook, its repository URL is shown in the
  dialog prompting whether to delete the linked repository file.
  However, wwhen deleting multiple notebooks, no repository URLs are
  shown. The rationale is that I found no intuitive way of presenting
  multiple URLs in the dialog along with their corresponding notebooks.
  • Loading branch information
amberin committed Jul 2, 2024
1 parent d4054cd commit 20b46d1
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 71 deletions.
109 changes: 109 additions & 0 deletions app/src/androidTest/java/com/orgzly/android/espresso/BooksTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static androidx.test.espresso.matcher.ViewMatchers.hasChildCount;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.orgzly.android.espresso.util.EspressoUtils.contextualToolbarOverflowMenu;
Expand All @@ -21,7 +25,9 @@
import static com.orgzly.android.espresso.util.EspressoUtils.onNoteInBook;
import static com.orgzly.android.espresso.util.EspressoUtils.onSnackbar;
import static com.orgzly.android.espresso.util.EspressoUtils.replaceTextCloseKeyboard;
import static com.orgzly.android.espresso.util.EspressoUtils.sync;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
Expand All @@ -41,6 +47,7 @@
import com.orgzly.R;
import com.orgzly.android.BookFormat;
import com.orgzly.android.OrgzlyTest;
import com.orgzly.android.repos.RepoType;
import com.orgzly.android.ui.main.MainActivity;

import org.junit.Before;
Expand Down Expand Up @@ -299,4 +306,106 @@ public void testBackPressClosesSelectionMenu() {
// Make sure we're still in the app
onBook(0, R.id.item_book_title).check(matches(withText("book-1")));
}

@Test
public void testSetLinkOnSingleBookCurrentRepoIsSelected() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo");
sync();
onBook(0, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(0).perform(longClick());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.books_context_menu_item_set_link)).perform(click());
onView(withText("mock://repo")).check(matches(isChecked()));
}

/**
* When setting the link of multiple books, no repo should be pre-selected,
* no matter how many repos there are, and no matter whether the books
* already have a link or not. The reason for this is that we have no
* intuitive way of displaying links to multiple repos.
*/
@Test
public void testSetLinkOnMultipleBooksNoRepoIsSelected() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo");
sync();
onBook(0, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(1, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(0).perform(longClick());
onBook(1).perform(click());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.books_context_menu_item_set_link)).perform(click());
onView(withText("mock://repo")).check(matches(isNotChecked()));
}

@Test
public void testDeleteSingleBookLinkedUrlIsShown() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo");
sync();
onBook(0, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(0).perform(longClick());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.delete)).perform(click());
onView(withText(R.string.also_delete_linked_book)).check(matches(isDisplayed()));
onView(withId(R.id.delete_linked_url)).check(matches(withText("mock://repo/book-1.org")));
}

@Test
public void testDeleteMultipleBooksLinkedUrlIsNotShown() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo");
sync();
onBook(0, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(1, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(0).perform(longClick());
onBook(1).perform(click());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.delete)).perform(click());
onView(withText(R.string.also_delete_linked_books)).check(matches(isDisplayed()));
onView(withId(R.id.delete_linked_url)).check(matches(withText("")));
}

@Test
public void testDeleteMultipleBooksWithNoLinks() {
onBook(0).perform(longClick());
onBook(1).perform(click());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.delete)).perform(click());
onView(withText(R.string.delete)).perform(click());
assert dataRepository.getBooks().size() == 1;
}

@Test
public void testDeleteMultipleBooksAndRooks() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo");
sync();
onBook(0, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(1, R.id.item_book_link_repo).check(matches(withText("mock://repo")));
onBook(0).perform(longClick());
onBook(1).perform(click());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.delete)).perform(click());
onView(withId(R.id.delete_linked_checkbox)).perform(click());
onView(withText(R.string.delete)).perform(click());
assert dataRepository.getBooks().size() == 1;
}

/**
* When multiple books are selected, the "rename" and "export" actions should be removed from
* the context menu. By also testing that only the expected number of actions are shown, we
* protect against someone later adding actions to the menu without fully considering the support for
* multiple selected books. When such support is added, this test will need to be updated.
*/
@Test
public void testMultipleBooksSelectedContextMenuShowsSupportedActionsOnly() {
onBook(0).perform(longClick());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.rename)).check(matches(isDisplayed()));
onView(withText(R.string.export)).check(matches(isDisplayed()));
onView(withClassName(containsString("MenuDropDownListView"))).check(matches(hasChildCount(4)));
pressBack();
onBook(1).perform(click());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.rename)).check(doesNotExist());
onView(withText(R.string.export)).check(doesNotExist());
onView(withClassName(containsString("MenuDropDownListView"))).check(matches(hasChildCount(2)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.DrawerActions.close;
import static androidx.test.espresso.contrib.DrawerActions.open;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
Expand All @@ -14,7 +13,6 @@
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.orgzly.android.espresso.util.EspressoUtils.clickSetting;
import static com.orgzly.android.espresso.util.EspressoUtils.contextualToolbarOverflowMenu;
import static com.orgzly.android.espresso.util.EspressoUtils.grantAlarmsAndRemindersPermission;
import static com.orgzly.android.espresso.util.EspressoUtils.onActionItemClick;
import static com.orgzly.android.espresso.util.EspressoUtils.onBook;
import static com.orgzly.android.espresso.util.EspressoUtils.onListItem;
Expand All @@ -24,6 +22,7 @@
import static com.orgzly.android.espresso.util.EspressoUtils.recyclerViewItemCount;
import static com.orgzly.android.espresso.util.EspressoUtils.replaceTextCloseKeyboard;
import static com.orgzly.android.espresso.util.EspressoUtils.settingsSetTodoKeywords;
import static com.orgzly.android.espresso.util.EspressoUtils.sync;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
Expand Down Expand Up @@ -69,16 +68,6 @@ public void tearDown() throws Exception {
}
}

/**
* Utility method for starting sync using drawer button.
*/
private void sync() {
grantAlarmsAndRemindersPermission();
onView(withId(R.id.drawer_layout)).perform(open());
onView(withId(R.id.sync_button_container)).perform(click());
onView(withId(R.id.drawer_layout)).perform(close());
}

@Test
public void testRunSync() {
scenario = ActivityScenario.launch(MainActivity.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.pressKey;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.contrib.DrawerActions.close;
import static androidx.test.espresso.contrib.DrawerActions.open;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
Expand Down Expand Up @@ -512,4 +514,14 @@ public static void grantAlarmsAndRemindersPermission() {
getInstrumentation().getUiAutomation().executeShellCommand(shellCmd);
}
}

/**
* Utility method for starting sync using drawer button.
*/
public static void sync() {
grantAlarmsAndRemindersPermission();
onView(withId(R.id.drawer_layout)).perform(open());
onView(withId(R.id.sync_button_container)).perform(click());
onView(withId(R.id.drawer_layout)).perform(close());
}
}
Loading

0 comments on commit 20b46d1

Please sign in to comment.