diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt index a724642a2..83ff17cad 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt @@ -80,6 +80,77 @@ class HistoryTest { } } + @Test + fun copyHistoryItemURLTest() { + val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(firstWebPage.url) { + verifyPageContent("Page content: 1") + }.openThreeDotMenu { + }.openLibrary { + }.openHistory { + }.openThreeDotMenu { + }.clickCopy { + verifyCopySnackBarText() + } + } + + @Test + fun shareHistoryItemTest() { + val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(firstWebPage.url) { + verifyPageContent("Page content: 1") + }.openThreeDotMenu { + }.openLibrary { + }.openHistory { + }.openThreeDotMenu { + }.clickShare { + verifyShareOverlay() + verifyShareTabFavicon() + verifyShareTabTitle() + verifyShareTabUrl() + } + } + + @Test + fun openHistoryItemInNewTabTest() { + val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(firstWebPage.url) { + verifyPageContent("Page content: 1") + }.openThreeDotMenu { + }.openLibrary { + }.openHistory { + }.openThreeDotMenu { + }.clickOpenInNormalTab { + verifyPageContent(firstWebPage.content) + }.openHomeScreen { + verifyOpenTabsHeader() + } + } + + @Test + fun openHistoryItemInNewPrivateTabTest() { + val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(firstWebPage.url) { + verifyPageContent("Page content: 1") + }.openThreeDotMenu { + }.openLibrary { + }.openHistory { + }.openThreeDotMenu { + }.clickOpenInPrivateTab { + verifyPageContent(firstWebPage.content) + }.openHomeScreen { + verifyPrivateSessionHeader() + } + } + @Test fun deleteHistoryItemTest() { val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) @@ -90,8 +161,8 @@ class HistoryTest { }.openThreeDotMenu { }.openLibrary { }.openHistory { - openOverflowMenu() - clickThreeDotMenuDelete() + }.openThreeDotMenu { + }.clickDelete { verifyEmptyHistoryView() } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt index 8d7a6be73..d88b6f277 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt @@ -55,6 +55,8 @@ class HistoryRobot { fun verifyTestPageUrl(expectedUrl: Uri) = assertPageUrl(expectedUrl) + fun verifyCopySnackBarText() = assertCopySnackBarText() + fun verifyDeleteConfirmationMessage() = assertDeleteConfirmationMessage() fun verifyHomeScreen() = HomeScreenRobot().verifyHomeScreen() @@ -66,11 +68,7 @@ class HistoryRobot { ), waitingTime ) - overflowMenu().click() - } - - fun clickThreeDotMenuDelete() { - threeDotMenuDeleteButton().click() + threeDotMenu().click() } fun clickDeleteHistoryButton() { @@ -92,6 +90,15 @@ class HistoryRobot { HistoryRobot().interact() return Transition() } + + fun openThreeDotMenu(interact: ThreeDotMenuHistoryItemRobot.() -> Unit): + ThreeDotMenuHistoryItemRobot.Transition { + + threeDotMenu().click() + + ThreeDotMenuHistoryItemRobot().interact() + return ThreeDotMenuHistoryItemRobot.Transition() + } } } @@ -106,9 +113,9 @@ private fun testPageTitle() = onView(allOf(withId(R.id.title), withText("Test_Pa private fun pageUrl() = onView(withId(R.id.url)) -private fun overflowMenu() = onView(withId(R.id.overflow_menu)) +private fun threeDotMenu() = onView(withId(R.id.overflow_menu)) -private fun threeDotMenuDeleteButton() = onView(withId(R.id.simple_text)) +private fun snackBarText() = onView(withId(R.id.snackbar_text)) private fun deleteAllHistoryButton() = onView(withId(R.id.delete_button)) @@ -143,3 +150,5 @@ private fun assertDeleteConfirmationMessage() = onView(withText("This will delete all of your browsing data.")) .inRoot(isDialog()) .check(matches(isDisplayed())) + +private fun assertCopySnackBarText() = snackBarText().check(matches(withText("URL copied"))) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuHistoryItemRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuHistoryItemRobot.kt new file mode 100644 index 000000000..cc44a1621 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuHistoryItemRobot.kt @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.ui.robots + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.click +import org.mozilla.fenix.helpers.ext.waitNotNull + +/** + * Implementation of Robot Pattern for the History three dot menu. + */ +class ThreeDotMenuHistoryItemRobot { + + class Transition { + + fun clickCopy(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition { + copyButton().click() + HistoryRobot().interact() + return HistoryRobot.Transition() + } + + fun clickShare(interact: LibrarySubMenusMultipleSelectionToolbarRobot.() -> Unit): + LibrarySubMenusMultipleSelectionToolbarRobot.Transition { + + shareButton().click() + + mDevice.waitNotNull( + Until.findObject( + By.text("ALL ACTIONS") + ), TestAssetHelper.waitingTime + ) + + LibrarySubMenusMultipleSelectionToolbarRobot().interact() + return LibrarySubMenusMultipleSelectionToolbarRobot.Transition() + } + + fun clickOpenInNormalTab(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + openInNewNormalTabButton().click() + BrowserRobot().interact() + return BrowserRobot.Transition() + } + + fun clickOpenInPrivateTab(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + openInNewPrivateTabButton().click() + BrowserRobot().interact() + return BrowserRobot.Transition() + } + + fun clickDelete(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition { + deleteButton().click() + HistoryRobot().interact() + return HistoryRobot.Transition() + } + } +} + +private fun copyButton() = onView(withText(R.string.history_menu_copy_button)) + +private fun shareButton() = onView(withText(R.string.history_menu_share_button)) + +private fun openInNewNormalTabButton() = + onView(withText(R.string.history_menu_open_in_new_tab_button)) + +private fun openInNewPrivateTabButton() = + onView(withText(R.string.history_menu_open_in_private_tab_button)) + +private fun deleteButton() = onView(withText(R.string.history_delete_item)) diff --git a/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt index d62f17386..62cbff6df 100644 --- a/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt @@ -4,20 +4,58 @@ package org.mozilla.fenix.library.history +import android.content.ClipData +import android.content.ClipboardManager +import android.content.res.Resources +import androidx.navigation.NavController +import androidx.navigation.NavDirections +import assertk.assertAll +import assertk.assertThat +import assertk.assertions.hasSize +import assertk.assertions.isEqualTo import io.mockk.every import io.mockk.mockk +import io.mockk.slot import io.mockk.verify +import mozilla.components.concept.engine.prompt.ShareData import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.TestApplication +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.FenixSnackbar +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +// Robolectric needed for `onShareItem()` +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) class HistoryControllerTest { - private val historyItem = HistoryItem(0, "title", "url", 0.toLong()) private val store: HistoryFragmentStore = mockk(relaxed = true) private val state: HistoryFragmentState = mockk(relaxed = true) + private val navController: NavController = mockk(relaxed = true) + private val resources: Resources = mockk(relaxed = true) + private val snackbar: FenixSnackbar = mockk(relaxed = true) + private val clipboardManager: ClipboardManager = mockk(relaxed = true) + private val openInBrowser: (HistoryItem, BrowsingMode?) -> Unit = mockk(relaxed = true) + private val displayDeleteAll: () -> Unit = mockk(relaxed = true) + private val invalidateOptionsMenu: () -> Unit = mockk(relaxed = true) + private val deleteHistoryItems: (Set) -> Unit = mockk(relaxed = true) + private val controller = DefaultHistoryController( + store, + navController, + resources, + snackbar, + clipboardManager, + openInBrowser, + displayDeleteAll, + invalidateOptionsMenu, + deleteHistoryItems + ) @Before fun setUp() { @@ -26,34 +64,35 @@ class HistoryControllerTest { @Test fun onPressHistoryItemInNormalMode() { - var historyItemReceived: HistoryItem? = null - - every { state.mode } returns HistoryFragmentState.Mode.Normal - - val controller = DefaultHistoryController( - store, - { historyItemReceived = it }, - mockk(), - mockk(), - mockk() - ) - controller.handleOpen(historyItem) - assertEquals(historyItem, historyItemReceived) + + verify { + openInBrowser(historyItem, null) + } + } + + @Test + fun onOpenItemInNormalMode() { + controller.handleOpen(historyItem, BrowsingMode.Normal) + + verify { + openInBrowser(historyItem, BrowsingMode.Normal) + } + } + + @Test + fun onOpenItemInPrivateMode() { + controller.handleOpen(historyItem, BrowsingMode.Private) + + verify { + openInBrowser(historyItem, BrowsingMode.Private) + } } @Test fun onPressHistoryItemInEditMode() { every { state.mode } returns HistoryFragmentState.Mode.Editing(setOf()) - val controller = DefaultHistoryController( - store, - { }, - mockk(), - mockk(), - mockk() - ) - controller.handleSelect(historyItem) verify { @@ -65,14 +104,6 @@ class HistoryControllerTest { fun onPressSelectedHistoryItemInEditMode() { every { state.mode } returns HistoryFragmentState.Mode.Editing(setOf(historyItem)) - val controller = DefaultHistoryController( - store, - { }, - mockk(), - mockk(), - mockk() - ) - controller.handleDeselect(historyItem) verify { @@ -84,7 +115,6 @@ class HistoryControllerTest { fun onBackPressedInNormalMode() { every { state.mode } returns HistoryFragmentState.Mode.Normal - val controller = DefaultHistoryController(store, mockk(), mockk(), mockk(), mockk()) assertFalse(controller.handleBackPressed()) } @@ -92,9 +122,7 @@ class HistoryControllerTest { fun onBackPressedInEditMode() { every { state.mode } returns HistoryFragmentState.Mode.Editing(setOf()) - val controller = DefaultHistoryController(store, mockk(), mockk(), mockk(), mockk()) assertTrue(controller.handleBackPressed()) - verify { store.dispatch(HistoryFragmentAction.ExitEditMode) } @@ -102,45 +130,76 @@ class HistoryControllerTest { @Test fun onModeSwitched() { - var menuInvalidated = false - val controller = DefaultHistoryController( - mockk(), - mockk(), - mockk(), - { menuInvalidated = true }, - mockk() - ) controller.handleModeSwitched() - assertEquals(true, menuInvalidated) + + verify { + invalidateOptionsMenu.invoke() + } } @Test fun onDeleteAll() { - var deleteAllDialogShown = false - val controller = DefaultHistoryController( - mockk(), - mockk(), - { deleteAllDialogShown = true }, - mockk(), - mockk() - ) controller.handleDeleteAll() - assertEquals(true, deleteAllDialogShown) + + verify { + displayDeleteAll.invoke() + } } @Test fun onDeleteSome() { - var itemsToDelete: Set? = null - val historyItem = HistoryItem(0, "title", "url", 0.toLong()) - val newHistoryItem = HistoryItem(1, "title", "url", 0.toLong()) - val controller = DefaultHistoryController( - mockk(), - mockk(), - mockk(), - mockk(), - { itemsToDelete = it } + val itemsToDelete = setOf(historyItem) + + controller.handleDeleteSome(itemsToDelete) + + verify { + deleteHistoryItems(itemsToDelete) + } + } + + @Test + fun onCopyItem() { + val clipdata = slot() + + controller.handleCopyUrl(historyItem) + + verify { + clipboardManager.primaryClip = capture(clipdata) + snackbar.show() + } + assertAll { + assertEquals(clipdata.captured.itemCount, 1) + assertThat(clipdata.captured.description.label).isEqualTo(historyItem.url) + assertThat(clipdata.captured.getItemAt(0).text).isEqualTo(historyItem.url) + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun onShareItem() { + val directions = slot() + + controller.handleShare(historyItem) + + // `verify` checks for referential equality. + // This would fail as the NavDirections are created and used in place in the tested method. + // Capture the NavDirections and `assert` for structural equality after. + verify { + navController.navigate( + capture(directions) ) - controller.handleDeleteSome(setOf(historyItem, newHistoryItem)) - assertEquals(itemsToDelete, setOf(historyItem, newHistoryItem)) + } + assertAll { + // The below class is private, can't easily assert using `instanceOf` + assertEquals( + directions.captured::class.simpleName, + "ActionHistoryFragmentToShareFragment" + ) + assertThat(directions.captured.arguments["data"] as Array).hasSize(1) + assertThat(((directions.captured.arguments["data"] as Array)[0]).title) + .isEqualTo(historyItem.title) + assertThat(((directions.captured.arguments["data"] as Array)[0]).url) + .isEqualTo(historyItem.url) + } } } diff --git a/app/src/test/java/org/mozilla/fenix/library/history/HistoryInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/library/history/HistoryInteractorTest.kt new file mode 100644 index 000000000..b413c957c --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/library/history/HistoryInteractorTest.kt @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.library.history + +import assertk.assertThat +import assertk.assertions.isTrue +import io.mockk.every +import io.mockk.mockk +import io.mockk.verifyAll +import org.junit.Test +import org.mozilla.fenix.browser.browsingmode.BrowsingMode + +class HistoryInteractorTest { + private val historyItem = HistoryItem(0, "title", "url", 0.toLong()) + val controller: HistoryController = mockk(relaxed = true) + val interactor = HistoryInteractor(controller) + + @Test + fun onOpen() { + interactor.open(historyItem) + + verifyAll { + controller.handleOpen(historyItem) + } + } + + @Test + fun onSelect() { + interactor.select(historyItem) + + verifyAll { + controller.handleSelect(historyItem) + } + } + + @Test + fun onDeselect() { + interactor.deselect(historyItem) + + verifyAll { + controller.handleDeselect(historyItem) + } + } + + @Test + fun onBackPressed() { + every { + controller.handleBackPressed() + } returns true + + val backpressHandled = interactor.onBackPressed() + + verifyAll { + controller.handleBackPressed() + } + assertThat(backpressHandled).isTrue() + } + + @Test + fun onModeSwitched() { + interactor.onModeSwitched() + + verifyAll { + controller.handleModeSwitched() + } + } + + @Test + fun onCopyPressed() { + interactor.onCopyPressed(historyItem) + + verifyAll { + controller.handleCopyUrl(historyItem) + } + } + + @Test + fun onSharePressed() { + interactor.onSharePressed(historyItem) + + verifyAll { + controller.handleShare(historyItem) + } + } + + @Test + fun onOpenInNormalTab() { + interactor.onOpenInNormalTab(historyItem) + + verifyAll { + controller.handleOpen(historyItem, BrowsingMode.Normal) + } + } + + @Test + fun onOpenInPrivateTab() { + interactor.onOpenInPrivateTab(historyItem) + + verifyAll { + controller.handleOpen(historyItem, BrowsingMode.Private) + } + } + + @Test + fun onDeleteAll() { + interactor.onDeleteAll() + + verifyAll { + controller.handleDeleteAll() + } + } + + @Test + fun onDeleteSome() { + val items = setOf(historyItem) + + interactor.onDeleteSome(items) + verifyAll { + controller.handleDeleteSome(items) + } + } +}