diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt index b69037e61a..b7932898a8 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.announcement.impl.fullscreen import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.announcement.api.Announcement import io.element.android.features.announcement.impl.AnnouncementEvent @@ -20,43 +23,39 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FullscreenAnnouncementViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back sends a AnnouncementEvent`() { + fun `clicking on back sends a AnnouncementEvent`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setFullscreenAnnouncementView( + setFullscreenAnnouncementView( anAnnouncementState( announcement = Announcement.Fullscreen.Space, eventSink = eventsRecorder, ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space)) } @Test - fun `clicking on Continue sends a AnnouncementEvent`() { + fun `clicking on Continue sends a AnnouncementEvent`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setFullscreenAnnouncementView( + setFullscreenAnnouncementView( anAnnouncementState( announcement = Announcement.Fullscreen.Space, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space)) } } -private fun AndroidComposeTestRule.setFullscreenAnnouncementView( +private fun AndroidComposeUiTest.setFullscreenAnnouncementView( state: AnnouncementState, ) { setContent { diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenViewTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenViewTest.kt index 35b90a6716..fed9f90de0 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenViewTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenViewTest.kt @@ -5,6 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.call.ui import android.view.KeyEvent @@ -12,8 +14,9 @@ import android.webkit.WebView import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.aPictureInPictureState @@ -24,9 +27,7 @@ import io.element.android.features.call.impl.ui.aCallScreenState import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.pressBackKey import org.junit.Assert.assertEquals -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config import org.robolectric.annotation.Implementation @@ -36,32 +37,29 @@ import org.robolectric.shadows.ShadowWebView @RunWith(AndroidJUnit4::class) class CallScreenViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `pressing back key triggers hangup when no web view is available and pip is unsupported`() { + fun `pressing back key triggers hangup when no web view is available and pip is unsupported`() = runAndroidComposeUiTest { val callEvents = EventsRecorder() - rule.setCallScreenView( + setCallScreenView( state = aCallScreenState(eventSink = callEvents), useInspectionMode = true, ) - rule.pressBackKey() + pressBackKey() callEvents.assertEmpty() } @Config(shadows = [RecordingShadowWebView::class]) @Test - fun `pressing back key dispatches escape key events to web view when pip is unsupported`() { - rule.setCallScreenView( + fun `pressing back key dispatches escape key events to web view when pip is unsupported`() = runAndroidComposeUiTest { + setCallScreenView( state = aCallScreenState(), useInspectionMode = false, ) - rule.pressBackKey() + pressBackKey() val dispatchedEvents = RecordingShadowWebView.dispatchedEvents assertEquals(2, dispatchedEvents.size) @@ -73,10 +71,10 @@ class CallScreenViewTest { @Config(shadows = [RecordingShadowWebView::class]) @Test - fun `web view javascript back handler emits pip event when pip is supported`() { + fun `web view javascript back handler emits pip event when pip is supported`() = runAndroidComposeUiTest { val pipEvents = EventsRecorder() - rule.setCallScreenView( + setCallScreenView( state = aCallScreenState(), useInspectionMode = false, pipState = aPictureInPictureState( @@ -85,7 +83,7 @@ class CallScreenViewTest { ), ) - rule.runOnIdle { + runOnIdle { RecordingShadowWebView.invokeJavascriptBackHandler() } @@ -95,7 +93,7 @@ class CallScreenViewTest { } } -private fun AndroidComposeTestRule.setCallScreenView( +private fun AndroidComposeUiTest.setCallScreenView( state: io.element.android.features.call.impl.ui.CallScreenState, useInspectionMode: Boolean, pipState: io.element.android.features.call.impl.pip.PictureInPictureState = aPictureInPictureState(supportPip = false), diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt index 26c942da1f..c672fd666b 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.logout.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.deactivation.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -26,33 +29,29 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressTag -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class AccountDeactivationViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState(eventSink = eventsRecorder), onBackClick = it, ) - rule.pressBack() + pressBack() } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on Deactivate emits the expected Event`() { + fun `clicking on Deactivate emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( password = A_PASSWORD, @@ -60,14 +59,14 @@ class AccountDeactivationViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_delete) + clickOn(CommonStrings.action_delete) eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false)) } @Test - fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() { + fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( password = A_PASSWORD, @@ -76,14 +75,14 @@ class AccountDeactivationViewTest { eventSink = eventsRecorder, ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false)) } @Test - fun `clicking on retry on the confirmation dialog emits the expected Event`() { + fun `clicking on retry on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( password = A_PASSWORD, @@ -92,26 +91,26 @@ class AccountDeactivationViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_retry) + clickOn(CommonStrings.action_retry) eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true)) } @Test - fun `switching on the erase all switch emits the expected Event`() { + fun `switching on the erase all switch emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_deactivate_account_delete_all_messages) + clickOn(R.string.screen_deactivate_account_delete_all_messages) eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true)) } @Test - fun `switching off the erase all switch emits the expected Event`() { + fun `switching off the erase all switch emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( eraseData = true, @@ -119,15 +118,15 @@ class AccountDeactivationViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_deactivate_account_delete_all_messages) + clickOn(R.string.screen_deactivate_account_delete_all_messages) eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false)) } @Config(qualifiers = "h1024dp") @Test - fun `typing text in the password field emits the expected Event`() { + fun `typing text in the password field emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAccountDeactivationView( + setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( password = A_PASSWORD, @@ -135,12 +134,12 @@ class AccountDeactivationViewTest { eventSink = eventsRecorder, ), ) - rule.onNodeWithTag(TestTags.loginPassword.value).performTextInput("A") + onNodeWithTag(TestTags.loginPassword.value).performTextInput("A") eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD")) } } -private fun AndroidComposeTestRule.setAccountDeactivationView( +private fun AndroidComposeUiTest.setAccountDeactivationView( state: AccountDeactivationState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt index f1e9bd8fc6..57a9f65099 100644 --- a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.forward.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId @@ -21,34 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressTag -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ForwardMessagesViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `cancel error emits the expected event`() { + fun `cancel error emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setForwardMessagesView( + setForwardMessagesView( aForwardMessagesState( forwardAction = AsyncAction.Failure(AN_EXCEPTION), eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(ForwardMessagesEvents.ClearError) } @Test - fun `success invokes onForwardSuccess`() { + fun `success invokes onForwardSuccess`() = runAndroidComposeUiTest { val data = listOf(A_ROOM_ID) val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam?>(data) { callback -> - rule.setForwardMessagesView( + setForwardMessagesView( aForwardMessagesState( forwardAction = AsyncAction.Success(data), eventSink = eventsRecorder @@ -59,7 +58,7 @@ class ForwardMessagesViewTest { } } -private fun AndroidComposeTestRule.setForwardMessagesView( +private fun AndroidComposeUiTest.setForwardMessagesView( state: ForwardMessagesState, onForwardSuccess: (List) -> Unit = EnsureNeverCalledWithParam(), ) { diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt index 521bf91b37..6e74f58f66 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.ftue.impl.sessionverification.choosemode import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.ftue.impl.R import io.element.android.libraries.architecture.AsyncData @@ -18,65 +21,61 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class ChooseSessionVerificationModeViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Config(qualifiers = "h1024dp") @Test - fun `clicking on learn more invokes the expected callback`() { + fun `clicking on learn more invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setChooseSelfVerificationModeView( + setChooseSelfVerificationModeView( aChooseSelfVerificationModeState(), onLearnMoreClick = callback, ) - rule.clickOn(CommonStrings.action_learn_more) + clickOn(CommonStrings.action_learn_more) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on use another device calls the callback`() { + fun `clicking on use another device calls the callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setChooseSelfVerificationModeView( + setChooseSelfVerificationModeView( aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))), onUseAnotherDevice = callback, ) - rule.clickOn(R.string.screen_identity_use_another_device) + clickOn(R.string.screen_identity_use_another_device) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on enter recovery key calls the callback`() { + fun `clicking on enter recovery key calls the callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setChooseSelfVerificationModeView( + setChooseSelfVerificationModeView( aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseRecoveryKey = true))), onEnterRecoveryKey = callback, ) - rule.clickOn(R.string.screen_identity_confirmation_use_recovery_key) + clickOn(R.string.screen_identity_confirmation_use_recovery_key) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on cannot confirm calls the reset keys callback`() { + fun `clicking on cannot confirm calls the reset keys callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setChooseSelfVerificationModeView( + setChooseSelfVerificationModeView( aChooseSelfVerificationModeState(), onResetKey = callback, ) - rule.clickOn(R.string.screen_identity_confirmation_cannot_confirm) + clickOn(R.string.screen_identity_confirmation_cannot_confirm) } } - private fun AndroidComposeTestRule.setChooseSelfVerificationModeView( + private fun AndroidComposeUiTest.setChooseSelfVerificationModeView( state: ChooseSelfVerificationModeState, onLearnMoreClick: () -> Unit = EnsureNeverCalled(), onUseAnotherDevice: () -> Unit = EnsureNeverCalled(), diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt index 4c361b47f3..de5760c1bd 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt @@ -6,10 +6,13 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.home.impl.filters import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.home.impl.R import io.element.android.features.home.impl.filters.selection.FilterSelectionState @@ -17,23 +20,20 @@ import io.element.android.libraries.testtags.TestTags import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressTag -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomListFiltersViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on filters generates expected Event`() { + fun `clicking on filters generates expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { RoomListFiltersView( state = aRoomListFiltersState(eventSink = eventsRecorder), ) } - rule.clickOn(R.string.screen_roomlist_filter_rooms) + clickOn(R.string.screen_roomlist_filter_rooms) eventsRecorder.assertList( listOf( RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms), @@ -42,9 +42,9 @@ class RoomListFiltersViewTest { } @Test - fun `clicking on clear filters generates expected Event`() { + fun `clicking on clear filters generates expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { RoomListFiltersView( state = aRoomListFiltersState( filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) }, @@ -52,7 +52,7 @@ class RoomListFiltersViewTest { ), ) } - rule.pressTag(TestTags.homeScreenClearFilters.value) + pressTag(TestTags.homeScreenClearFilters.value) eventsRecorder.assertList( listOf( RoomListFiltersEvent.ClearSelectedFilters, diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt index 6be5fe4c16..5fa2adf9d6 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.home.impl.roomlist import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.home.impl.R import io.element.android.libraries.matrix.api.core.RoomId @@ -20,23 +23,20 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomListContextMenuTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on Mark as read generates expected Events`() { + fun `clicking on Mark as read generates expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown(hasNewContent = true) - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, eventSink = eventsRecorder, ) - rule.clickOn(R.string.screen_roomlist_mark_as_read) + clickOn(R.string.screen_roomlist_mark_as_read) eventsRecorder.assertList( listOf( RoomListEvent.HideContextMenu, @@ -46,14 +46,14 @@ class RoomListContextMenuTest { } @Test - fun `clicking on Mark as unread generates expected Events`() { + fun `clicking on Mark as unread generates expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown(hasNewContent = false) - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, eventSink = eventsRecorder, ) - rule.clickOn(R.string.screen_roomlist_mark_as_unread) + clickOn(R.string.screen_roomlist_mark_as_unread) eventsRecorder.assertList( listOf( RoomListEvent.HideContextMenu, @@ -63,14 +63,14 @@ class RoomListContextMenuTest { } @Test - fun `clicking on Leave room generates expected Events`() { + fun `clicking on Leave room generates expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown(isDm = false) - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, eventSink = eventsRecorder, ) - rule.clickOn(CommonStrings.action_leave_room) + clickOn(CommonStrings.action_leave_room) eventsRecorder.assertList( listOf( RoomListEvent.HideContextMenu, @@ -80,48 +80,48 @@ class RoomListContextMenuTest { } @Test - fun `clicking on Report room invokes the expected callback and generates expected Event`() { + fun `clicking on Report room invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown() val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit) - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, canReportRoom = true, eventSink = eventsRecorder, onRoomSettingsClick = EnsureNeverCalledWithParam(), onReportRoomClick = callback, ) - rule.clickOn(CommonStrings.action_report_room) + clickOn(CommonStrings.action_report_room) eventsRecorder.assertSingle(RoomListEvent.HideContextMenu) callback.assertSuccess() } @Test - fun `clicking on Settings invokes the expected callback and generates expected Event`() { + fun `clicking on Settings invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown() val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit) - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, eventSink = eventsRecorder, onRoomSettingsClick = callback, ) - rule.clickOn(CommonStrings.common_settings) + clickOn(CommonStrings.common_settings) eventsRecorder.assertSingle(RoomListEvent.HideContextMenu) callback.assertSuccess() } @Test - fun `clicking on Favourites generates expected Event`() { + fun `clicking on Favourites generates expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val contextMenu = aContextMenuShown(isDm = false, isFavorite = false) val callback = EnsureNeverCalledWithParam() - rule.setRoomListContextMenu( + setRoomListContextMenu( contextMenu = contextMenu, eventSink = eventsRecorder, onRoomSettingsClick = callback, ) - rule.clickOn(CommonStrings.common_favourite) + clickOn(CommonStrings.common_favourite) eventsRecorder.assertList( listOf( RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true), @@ -129,7 +129,7 @@ class RoomListContextMenuTest { ) } - private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu( + private fun AndroidComposeUiTest.setRoomListContextMenu( contextMenu: RoomListState.ContextMenu.Shown, canReportRoom: Boolean = false, eventSink: (RoomListEvent) -> Unit, diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt index d7f509fda4..c8bba05e52 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt @@ -6,10 +6,12 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.home.impl.roomlist -import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.home.impl.model.aRoomListRoomSummary import io.element.android.libraries.ui.strings.CommonStrings @@ -18,19 +20,16 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomListDeclineInviteMenuTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on decline emits the expected Events`() { + fun `clicking on decline emits the expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) - rule.setSafeContent { + setSafeContent { RoomListDeclineInviteMenu( menu = menu, canReportRoom = false, @@ -38,7 +37,7 @@ class RoomListDeclineInviteMenuTest { eventSink = eventsRecorder, ) } - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertList( listOf( RoomListEvent.HideDeclineInviteMenu, @@ -48,10 +47,10 @@ class RoomListDeclineInviteMenuTest { } @Test - fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() { + fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) - rule.setSafeContent { + setSafeContent { RoomListDeclineInviteMenu( menu = menu, canReportRoom = true, @@ -59,16 +58,16 @@ class RoomListDeclineInviteMenuTest { eventSink = eventsRecorder, ) } - rule.clickOn(CommonStrings.action_decline_and_block) + clickOn(CommonStrings.action_decline_and_block) val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu) eventsRecorder.assertList(expectedEvents) } @Test - fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() { + fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) - rule.setSafeContent { + setSafeContent { RoomListDeclineInviteMenu( menu = menu, canReportRoom = false, @@ -76,7 +75,7 @@ class RoomListDeclineInviteMenuTest { eventSink = eventsRecorder, ) } - rule.clickOn(CommonStrings.action_decline_and_block) + clickOn(CommonStrings.action_decline_and_block) val expectedEvents = listOf( RoomListEvent.HideDeclineInviteMenu, RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true), @@ -85,10 +84,10 @@ class RoomListDeclineInviteMenuTest { } @Test - fun `clicking on cancel emits the expected Event`() { + fun `clicking on cancel emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) - rule.setSafeContent { + setSafeContent { RoomListDeclineInviteMenu( menu = menu, canReportRoom = false, @@ -96,7 +95,7 @@ class RoomListDeclineInviteMenuTest { eventSink = eventsRecorder, ) } - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu)) } } diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt index 8402a921ca..b8d61994fa 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt @@ -6,16 +6,19 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.home.impl.roomlist import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.longClick import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.home.impl.HomeView import io.element.android.features.home.impl.R @@ -32,22 +35,17 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class RoomListViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Config(qualifiers = "h1024dp") @Test - fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() { + fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomListView( + setRoomListView( state = aRoomListState( contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), eventSink = eventsRecorder, @@ -62,9 +60,9 @@ class RoomListViewTest { } @Test - fun `clicking on close recovery key banner emits the expected Event`() { + fun `clicking on close recovery key banner emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomListView( + setRoomListView( state = aRoomListState( contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), eventSink = eventsRecorder, @@ -74,15 +72,15 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - val close = rule.activity.getString(CommonStrings.action_close) - rule.onNodeWithContentDescription(close).performClick() + val close = activity!!.getString(CommonStrings.action_close) + onNodeWithContentDescription(close).performClick() eventsRecorder.assertSingle(RoomListEvent.DismissBanner) } @Test - fun `clicking on close setup key banner emits the expected Event`() { + fun `clicking on close setup key banner emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomListView( + setRoomListView( state = aRoomListState( contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery), eventSink = eventsRecorder, @@ -92,16 +90,16 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - val close = rule.activity.getString(CommonStrings.action_close) - rule.onNodeWithContentDescription(close).performClick() + val close = activity!!.getString(CommonStrings.action_close) + onNodeWithContentDescription(close).performClick() eventsRecorder.assertSingle(RoomListEvent.DismissBanner) } @Test - fun `clicking on continue recovery key banner invokes the expected callback`() { + fun `clicking on continue recovery key banner invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { callback -> - rule.setRoomListView( + setRoomListView( state = aRoomListState( contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), eventSink = eventsRecorder, @@ -112,17 +110,17 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertEmpty() } } @Test - fun `clicking on continue setup key banner invokes the expected callback`() { + fun `clicking on continue setup key banner invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { callback -> - rule.setRoomListView( + setRoomListView( state = aRoomListState( contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery), eventSink = eventsRecorder, @@ -131,28 +129,28 @@ class RoomListViewTest { ) // Remove automatic initial events eventsRecorder.clear() - rule.clickOn(R.string.banner_set_up_recovery_submit) + clickOn(R.string.banner_set_up_recovery_submit) eventsRecorder.assertEmpty() } } @Test - fun `clicking on start chat when the session has no room invokes the expected callback`() { + fun `clicking on start chat when the session has no room invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setRoomListView( + setRoomListView( state = aRoomListState( eventSink = eventsRecorder, contentState = anEmptyContentState(), ), onCreateRoomClick = callback, ) - rule.clickOn(CommonStrings.action_start_chat) + clickOn(CommonStrings.action_start_chat) } } @Test - fun `clicking on a room invokes the expected callback`() { + fun `clicking on a room invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomListState( eventSink = eventsRecorder, @@ -161,7 +159,7 @@ class RoomListViewTest { it.displayType == RoomSummaryDisplayType.ROOM } ensureCalledOnceWithParam(room0.roomId) { callback -> - rule.setRoomListView( + setRoomListView( state = state, onRoomClick = callback, ) @@ -169,14 +167,14 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.latestEvent.content().toString()).performClick() + onNodeWithText(room0.latestEvent.content().toString()).performClick() } eventsRecorder.assertEmpty() } @Test - fun `clicking on a room twice invokes the expected callback only once`() { + fun `clicking on a room twice invokes the expected callback only once`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomListState( eventSink = eventsRecorder, @@ -185,13 +183,13 @@ class RoomListViewTest { it.displayType == RoomSummaryDisplayType.ROOM } ensureCalledOnceWithParam(room0.roomId) { callback -> - rule.setRoomListView( + setRoomListView( state = state, onRoomClick = callback, ) // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.latestEvent.content().toString()) + onNodeWithText(room0.latestEvent.content().toString()) .performClick() .performClick() } @@ -199,7 +197,7 @@ class RoomListViewTest { } @Test - fun `long clicking on a room emits the expected Event`() { + fun `long clicking on a room emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomListState( eventSink = eventsRecorder, @@ -207,18 +205,18 @@ class RoomListViewTest { val room0 = state.contentAsRooms().summaries.first { it.displayType == RoomSummaryDisplayType.ROOM } - rule.setRoomListView( + setRoomListView( state = state, ) // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() } + onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() } eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0)) } @Test - fun `clicking on a room setting invokes the expected callback and emits expected Event`() { + fun `clicking on a room setting invokes the expected callback and emits expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomListState( contextMenu = aContextMenuShown(), @@ -226,7 +224,7 @@ class RoomListViewTest { ) val room0 = (state.contextMenu as RoomListState.ContextMenu.Shown).roomId ensureCalledOnceWithParam(room0) { callback -> - rule.setRoomListView( + setRoomListView( state = state, onRoomSettingsClick = callback, ) @@ -234,14 +232,14 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - rule.clickOn(CommonStrings.common_settings) + clickOn(CommonStrings.common_settings) } eventsRecorder.assertSingle(RoomListEvent.HideContextMenu) } @Test - fun `clicking on accept and decline invite emits the expected Events`() { + fun `clicking on accept and decline invite emits the expected Events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomListState( eventSink = eventsRecorder, @@ -249,13 +247,13 @@ class RoomListViewTest { val invitedRoom = state.contentAsRooms().summaries.first { it.displayType == RoomSummaryDisplayType.INVITE } - rule.setRoomListView(state = state) + setRoomListView(state = state) // Remove automatic initial events eventsRecorder.clear() - rule.clickOn(CommonStrings.action_accept) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_accept) + clickOn(CommonStrings.action_decline) eventsRecorder.assertList( listOf( RoomListEvent.AcceptInvite(invitedRoom), @@ -265,7 +263,7 @@ class RoomListViewTest { } } -private fun AndroidComposeTestRule.setRoomListView( +private fun AndroidComposeUiTest.setRoomListView( state: RoomListState, onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onSettingsClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt index 5c1325b107..d612d765b6 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersViewTest.kt @@ -5,34 +5,32 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.home.impl.spacefilters import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.test.A_ROOM_ALIAS import io.element.android.tests.testutils.EventsRecorder -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SpaceFiltersViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on a filter with alias shows display name and alias`() { + fun `clicking on a filter with alias shows display name and alias`() = runAndroidComposeUiTest { val filter = aSpaceServiceFilter( displayName = "Test Space", canonicalAlias = A_ROOM_ALIAS, ) val eventsRecorder = EventsRecorder() - rule.setSpaceFiltersView( + setSpaceFiltersView( state = aSelectingSpaceFiltersState( availableFilters = listOf(filter), eventSink = eventsRecorder, @@ -40,20 +38,20 @@ class SpaceFiltersViewTest { ) // Both display name and alias should be visible - rule.onNodeWithText(filter.spaceRoom.displayName).assertExists() - rule.onNodeWithText(A_ROOM_ALIAS.value).assertExists() + onNodeWithText(filter.spaceRoom.displayName).assertExists() + onNodeWithText(A_ROOM_ALIAS.value).assertExists() - rule.onNodeWithText(filter.spaceRoom.displayName).performClick() + onNodeWithText(filter.spaceRoom.displayName).performClick() eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter)) } @Test - fun `multiple filters are displayed and clickable`() { + fun `multiple filters are displayed and clickable`() = runAndroidComposeUiTest { val filter1 = aSpaceServiceFilter(displayName = "Space One") val filter2 = aSpaceServiceFilter(displayName = "Space Two") val eventsRecorder = EventsRecorder() - rule.setSpaceFiltersView( + setSpaceFiltersView( state = aSelectingSpaceFiltersState( availableFilters = listOf(filter1, filter2), eventSink = eventsRecorder, @@ -61,17 +59,17 @@ class SpaceFiltersViewTest { ) // Both filters should be visible - rule.onNodeWithText(filter1.spaceRoom.displayName).assertExists() - rule.onNodeWithText(filter2.spaceRoom.displayName).assertExists() + onNodeWithText(filter1.spaceRoom.displayName).assertExists() + onNodeWithText(filter2.spaceRoom.displayName).assertExists() // Click on second filter - rule.onNodeWithText(filter2.spaceRoom.displayName).performClick() + onNodeWithText(filter2.spaceRoom.displayName).performClick() eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2)) } } -private fun AndroidComposeTestRule.setSpaceFiltersView( +private fun AndroidComposeUiTest.setSpaceFiltersView( state: SpaceFiltersState, ) { setContent { diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt index 299fec8565..e915696de4 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.invite.impl.declineandblock import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.invite.impl.R import io.element.android.libraries.ui.strings.CommonStrings @@ -21,98 +24,94 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DeclineAndBlockViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke the expected callback`() { + fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( eventSink = eventsRecorder, ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on decline when enabled emits the expected event`() { + fun `clicking on decline when enabled emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( blockUser = true, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertSingle(DeclineAndBlockEvents.Decline) } @Test - fun `clicking on decline when disabled does not emit event`() { + fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( blockUser = false, reportRoom = false, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertEmpty() } @Test - fun `clicking on block option emits the expected event`() { + fun `clicking on block option emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( blockUser = true, eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_decline_and_block_block_user_option_title) + clickOn(R.string.screen_decline_and_block_block_user_option_title) eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleBlockUser) } @Test - fun `clicking on report room option emits the expected event`() { + fun `clicking on report room option emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( reportRoom = true, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_report_room) + clickOn(CommonStrings.action_report_room) eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleReportRoom) } @Test - fun `typing text in the reason field emits the expected Event`() { + fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDeclineAndBlockView( + setDeclineAndBlockView( aDeclineAndBlockState( reportRoom = true, reportReason = "", eventSink = eventsRecorder, ), ) - rule.onNodeWithText("").performTextInput("Spam!") + onNodeWithText("").performTextInput("Spam!") eventsRecorder.assertSingle(DeclineAndBlockEvents.UpdateReportReason("Spam!")) } } -private fun AndroidComposeTestRule.setDeclineAndBlockView( +private fun AndroidComposeUiTest.setDeclineAndBlockView( state: DeclineAndBlockState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index 0a3b1ca3c6..e60d7da691 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.joinroom.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.invite.api.InviteData import io.element.android.features.invite.test.anInviteData @@ -26,116 +29,112 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class JoinRoomViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke the expected callback`() { + fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( eventSink = eventsRecorder, ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on Join room on CanJoin room emits the expected Event`() { + fun `clicking on Join room on CanJoin room emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_join_room_join_action) + clickOn(R.string.screen_join_room_join_action) eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom) } @Test - fun `clicking on Knock room on CanKnock room emits the expected Event`() { + fun `clicking on Knock room on CanKnock room emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), knockMessage = "Knock knock", eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_join_room_knock_action) + clickOn(R.string.screen_join_room_knock_action) eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom) } @Test - fun `clicking on closing Knock error emits the expected Event`() { + fun `clicking on closing Knock error emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), knockAction = AsyncAction.Failure(Exception("Error")), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } @Test - fun `clicking on cancel knock request emit the expected Event`() { + fun `clicking on cancel knock request emit the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_join_room_cancel_knock_action) + clickOn(R.string.screen_join_room_cancel_knock_action) eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true)) } @Test - fun `clicking on closing Cancel Knock error emits the expected Event`() { + fun `clicking on closing Cancel Knock error emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), cancelKnockAction = AsyncAction.Failure(Exception("Error")), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } @Test - fun `clicking on closing Join error emits the expected Event`() { + fun `clicking on closing Join error emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), joinAction = AsyncAction.Failure(Exception("Error")), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) } @Test - fun `when joining room is successful, the expected callback is invoked`() { + fun `when joining room is successful, the expected callback is invoked`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( joinAction = AsyncAction.Success(Unit), eventSink = eventsRecorder, @@ -146,53 +145,55 @@ class JoinRoomViewTest { } @Test - fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() { + fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val inviteData = anInviteData() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_accept) + clickOn(CommonStrings.action_accept) eventsRecorder.assertSingle(JoinRoomEvents.AcceptInvite(inviteData)) } @Test - fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() { + fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val inviteData = anInviteData() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, false)) } @Test fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and can report room, the expected callback is invoked`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - val inviteData = anInviteData() - val joinRoomState = aJoinRoomState( - contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())), - canReportRoom = true, - eventSink = eventsRecorder, - ) - ensureCalledOnceWithParam(inviteData) { - rule.setJoinRoomView( - state = joinRoomState, - onDeclineInviteAndBlockUser = it, + runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder(expectEvents = false) + val inviteData = anInviteData() + val joinRoomState = aJoinRoomState( + contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())), + canReportRoom = true, + eventSink = eventsRecorder, ) - rule.clickOn(R.string.screen_join_room_decline_and_block_button_title) + ensureCalledOnceWithParam(inviteData) { + setJoinRoomView( + state = joinRoomState, + onDeclineInviteAndBlockUser = it, + ) + clickOn(R.string.screen_join_room_decline_and_block_button_title) + } } } @Test - fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() { + fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val inviteData = anInviteData() val joinRoomState = aJoinRoomState( @@ -200,29 +201,29 @@ class JoinRoomViewTest { canReportRoom = false, eventSink = eventsRecorder, ) - rule.setJoinRoomView(state = joinRoomState) - rule.clickOn(R.string.screen_join_room_decline_and_block_button_title) + setJoinRoomView(state = joinRoomState) + clickOn(R.string.screen_join_room_decline_and_block_button_title) eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true)) } @Test - fun `clicking on Retry when an error occurs emits the expected Event`() { + fun `clicking on Retry when an error occurs emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aFailureContentState(), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_retry) + clickOn(CommonStrings.action_retry) eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent) } @Test - fun `clicking on ok when user is unauthorized the expected callback`() { + fun `clicking on ok when user is unauthorized the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(), joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin), @@ -230,25 +231,25 @@ class JoinRoomViewTest { ), onBackClick = it ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) } } @Test - fun `clicking on forget when user is banned invokes the expected callback`() { + fun `clicking on forget when user is banned invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomView( + setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null, null)), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_join_room_forget_action) + clickOn(R.string.screen_join_room_forget_action) eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom) } } -private fun AndroidComposeTestRule.setJoinRoomView( +private fun AndroidComposeUiTest.setJoinRoomView( state: JoinRoomState, onBackClick: () -> Unit = EnsureNeverCalled(), onJoinSuccess: () -> Unit = EnsureNeverCalled(), diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt index a9fea0905e..fc1600d8c8 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.knockrequests.impl.banner import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable @@ -21,35 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class KnockRequestsBannerViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on view on single request invoke the expected callback`() { + fun `clicking on view on single request invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setKnockRequestsBannerView( + setKnockRequestsBannerView( state = aKnockRequestsBannerState( eventSink = eventsRecorder, ), onViewRequestsClick = it ) - rule.clickOn(R.string.screen_room_single_knock_request_view_button_title) + clickOn(R.string.screen_room_single_knock_request_view_button_title) } } @Test - fun `clicking on view all when multiple requests invoke the expected callback`() { + fun `clicking on view all when multiple requests invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setKnockRequestsBannerView( + setKnockRequestsBannerView( state = aKnockRequestsBannerState( knockRequests = listOf( aKnockRequestPresentable(displayName = "Alice"), @@ -60,37 +58,37 @@ class KnockRequestsBannerViewTest { ), onViewRequestsClick = it ) - rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title) + clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title) } } @Test - fun `clicking on accept on a single request emit the expected event`() { + fun `clicking on accept on a single request emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setKnockRequestsBannerView( + setKnockRequestsBannerView( state = aKnockRequestsBannerState( eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_accept) + clickOn(CommonStrings.action_accept) eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest) } @Test - fun `clicking on dismiss emit the expected event`() { + fun `clicking on dismiss emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setKnockRequestsBannerView( + setKnockRequestsBannerView( state = aKnockRequestsBannerState( eventSink = eventsRecorder, ), ) - val close = rule.activity.getString(CommonStrings.action_close) - rule.onNodeWithContentDescription(close).performClick() + val close = activity!!.getString(CommonStrings.action_close) + onNodeWithContentDescription(close).performClick() eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss) } } -private fun AndroidComposeTestRule.setKnockRequestsBannerView( +private fun AndroidComposeUiTest.setKnockRequestsBannerView( state: KnockRequestsBannerState, onViewRequestsClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt index 188dcc7e56..14cac7a9b7 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.knockrequests.impl.list import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable @@ -23,90 +26,86 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.persistentListOf -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class KnockRequestsListViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke the expected callback`() { + fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( eventSink = eventsRecorder, ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on accept emit the expected event`() { + fun `clicking on accept emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_accept) + clickOn(CommonStrings.action_accept) eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest)) } @Test - fun `clicking on decline emit the expected event`() { + fun `clicking on decline emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest)) } @Test - fun `clicking on decline and ban emit the expected event`() { + fun `clicking on decline and ban emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(persistentListOf(knockRequest)), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title) + clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title) eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest)) } @Test - fun `clicking on accept all emit the expected event`() { + fun `clicking on accept all emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title) + clickOn(R.string.screen_knock_requests_list_accept_all_button_title) eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll) } @Test - fun `retry on async view retry emit the expected event`() { + fun `retry on async view retry emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")), @@ -114,15 +113,15 @@ class KnockRequestsListViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_retry) + clickOn(CommonStrings.action_retry) eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction) } @Test - fun `canceling async view emit the expected event`() { + fun `canceling async view emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")), @@ -130,15 +129,15 @@ class KnockRequestsListViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction) } @Test - fun `confirming async view emit the expected event`() { + fun `confirming async view emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) - rule.setKnockRequestsListView( + setKnockRequestsListView( aKnockRequestsListState( knockRequests = AsyncData.Success(knockRequests), asyncAction = AsyncAction.ConfirmingNoParams, @@ -146,12 +145,12 @@ class KnockRequestsListViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title) + clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title) eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction) } } -private fun AndroidComposeTestRule.setKnockRequestsListView( +private fun AndroidComposeUiTest.setKnockRequestsListView( state: KnockRequestsListState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt index ac0a129f49..7609acf809 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt @@ -5,11 +5,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.desktop import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.linknewdevice.impl.R import io.element.android.tests.testutils.EnsureNeverCalled @@ -18,42 +21,37 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DesktopNoticeViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( state = aDesktopNoticeState(), onBackClicked = callback, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on back button clicked - calls the expected callback`() { + fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( state = aDesktopNoticeState(), onBackClicked = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `when can continue - calls the expected callback`() { + fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( state = aDesktopNoticeState(canContinue = true), onReadyToScanClick = callback, ) @@ -61,16 +59,16 @@ class DesktopNoticeViewTest { } @Test - fun `on submit button clicked - emits the Continue event`() { + fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder() - rule.setView( + setView( state = aDesktopNoticeState(eventSink = eventRecorder), ) - rule.clickOn(R.string.screen_link_new_device_desktop_submit) + clickOn(R.string.screen_link_new_device_desktop_submit) eventRecorder.assertSingle(DesktopNoticeEvent.Continue) } - private fun AndroidComposeTestRule.setView( + private fun AndroidComposeUiTest.setView( state: DesktopNoticeState, onBackClicked: () -> Unit = EnsureNeverCalled(), onReadyToScanClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt index aa52a70149..b63d7471ac 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt @@ -5,58 +5,56 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.error import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ErrorViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the onCancel callback`() { + fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setErrorView( + setErrorView( onCancel = callback, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on try again button clicked - calls the expected callback`() { + fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setErrorView( + setErrorView( onRetry = callback ) - rule.clickOn(CommonStrings.action_try_again) + clickOn(CommonStrings.action_try_again) } } @Test - fun `on cancel button clicked - calls the expected callback`() { + fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setErrorView( + setErrorView( onCancel = callback ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) } } - private fun AndroidComposeTestRule.setErrorView( + private fun AndroidComposeUiTest.setErrorView( onRetry: () -> Unit = EnsureNeverCalled(), onCancel: () -> Unit = EnsureNeverCalled(), errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError, diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt index 20e1d898dd..25dc9efa8a 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt @@ -5,13 +5,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.number import androidx.activity.ComponentActivity +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled @@ -20,65 +23,60 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EnterNumberViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( state = aEnterNumberState(), onBackClicked = callback, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on back button clicked - calls the expected callback`() { + fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( state = aEnterNumberState(), onBackClicked = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `on continue button clicked - emits the Continue event`() { + fun `on continue button clicked - emits the Continue event`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder() - rule.setView( + setView( state = aEnterNumberState( number = "12", eventSink = eventRecorder, ), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventRecorder.assertSingle(EnterNumberEvent.Continue) } @Test - fun `when the number is not complete, continue button is disabled`() { + fun `when the number is not complete, continue button is disabled`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( state = aEnterNumberState( number = "1", eventSink = eventRecorder, ), ) - val continueStr = rule.activity.getString(CommonStrings.action_continue) - rule.onNodeWithText(continueStr).assertIsNotEnabled() + val continueStr = activity!!.getString(CommonStrings.action_continue) + onNodeWithText(continueStr).assertIsNotEnabled() } - private fun AndroidComposeTestRule.setView( + private fun AndroidComposeUiTest.setView( state: EnterNumberState, onBackClicked: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt index c6c89ba818..d552c2bff6 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt @@ -5,36 +5,34 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.qrcode import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShowQrCodeViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setView( + setView( onBackClick = callback ) - rule.pressBackKey() + pressBackKey() } } - private fun AndroidComposeTestRule.setView( + private fun AndroidComposeUiTest.setView( onBackClick: () -> Unit = EnsureNeverCalled(), ) { setContent { diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt index e352debfb0..bceb8753b2 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt @@ -5,11 +5,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.linknewdevice.impl.R import io.element.android.libraries.architecture.AsyncData @@ -19,74 +22,69 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LinkNewDeviceRootViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the onRetry callback`() { + fun `on back pressed - calls the onRetry callback`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLinkNewDeviceRootView( + setLinkNewDeviceRootView( state = aLinkNewDeviceRootState( eventSink = eventRecorder, ), onBackClick = callback ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `link desktop button clicked - calls the expected callback`() { + fun `link desktop button clicked - calls the expected callback`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLinkNewDeviceRootView( + setLinkNewDeviceRootView( state = aLinkNewDeviceRootState( isSupported = AsyncData.Success(true), eventSink = eventRecorder, ), onLinkDesktopDeviceClick = callback, ) - rule.clickOn(R.string.screen_link_new_device_root_desktop_computer) + clickOn(R.string.screen_link_new_device_root_desktop_computer) } } @Test - fun `link mobile button clicked - emits the expected event`() { + fun `link mobile button clicked - emits the expected event`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder() - rule.setLinkNewDeviceRootView( + setLinkNewDeviceRootView( state = aLinkNewDeviceRootState( isSupported = AsyncData.Success(true), eventSink = eventRecorder, ) ) - rule.clickOn(R.string.screen_link_new_device_root_mobile_device) + clickOn(R.string.screen_link_new_device_root_mobile_device) eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice) } @Test - fun `not supported - dismiss click - invokes the expected callback`() { + fun `not supported - dismiss click - invokes the expected callback`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLinkNewDeviceRootView( + setLinkNewDeviceRootView( state = aLinkNewDeviceRootState( isSupported = AsyncData.Success(false), eventSink = eventRecorder, ), onBackClick = callback, ) - rule.clickOn(CommonStrings.action_dismiss) + clickOn(CommonStrings.action_dismiss) } } - private fun AndroidComposeTestRule.setLinkNewDeviceRootView( + private fun AndroidComposeUiTest.setLinkNewDeviceRootView( state: LinkNewDeviceRootState = aLinkNewDeviceRootState(), onBackClick: () -> Unit = EnsureNeverCalled(), onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt index fcc3afeb7d..1932718fef 100644 --- a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt @@ -5,11 +5,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.linknewdevice.impl.screens.scan import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.test.AN_EXCEPTION @@ -19,44 +22,39 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ScanQrCodeViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( state = aScanQrCodeState( eventSink = eventRecorder, ), onBackClick = callback ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `try again button clicked - emits the expected event`() { + fun `try again button clicked - emits the expected event`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder() - rule.setView( + setView( state = aScanQrCodeState( scanAction = AsyncAction.Failure(AN_EXCEPTION), eventSink = eventRecorder, ) ) - rule.clickOn(CommonStrings.action_try_again) + clickOn(CommonStrings.action_try_again) eventRecorder.assertSingle(ScanQrCodeEvent.TryAgain) } - private fun AndroidComposeTestRule.setView( + private fun AndroidComposeUiTest.setView( state: ScanQrCodeState = aScanQrCodeState(), onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt index 317fbf8fed..63c19ba913 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationViewTest.kt @@ -5,15 +5,18 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.location.impl.share import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.libraries.testtags.TestTags @@ -23,102 +26,98 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShareLocationViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `test back action`() { + fun `test back action`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setShareLocationView( + setShareLocationView( state = aShareLocationState( eventSink = eventsRecorder ), navigateUp = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `test fab click`() { + fun `test fab click`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( eventSink = eventsRecorder ), navigateUp = EnsureNeverCalled(), ) - rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick() + onNodeWithTag(TestTags.floatingActionButton.value).performClick() eventsRecorder.assertSingle(ShareLocationEvent.StartTrackingUserLocation) } @Test - fun `when permission denied is displayed user can open the settings`() { + fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied), eventSink = eventsRecorder ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ShareLocationEvent.OpenAppSettings) } @Test - fun `when permission denied is displayed user can close the dialog`() { + fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied), eventSink = eventsRecorder ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog) } @Test - fun `when permission rationale is displayed user can request permissions`() { + fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale), eventSink = eventsRecorder ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ShareLocationEvent.RequestPermissions) } @Test - fun `when permission rationale is displayed user can close the dialog`() { + fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale), eventSink = eventsRecorder ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog) } @Test - fun `when location service disabled is displayed user can open location settings`() { + fun `when location service disabled is displayed user can open location settings`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled), hasLocationPermission = true, @@ -126,14 +125,14 @@ class ShareLocationViewTest { ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ShareLocationEvent.OpenLocationSettings) } @Test - fun `when location service disabled is displayed user can close the dialog`() { + fun `when location service disabled is displayed user can close the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShareLocationView( + setShareLocationView( aShareLocationState( dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled), hasLocationPermission = true, @@ -141,12 +140,12 @@ class ShareLocationViewTest { ), navigateUp = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog) } } -private fun AndroidComposeTestRule.setShareLocationView( +private fun AndroidComposeUiTest.setShareLocationView( state: ShareLocationState, navigateUp: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt index fecbbdbf89..45ed894f97 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt @@ -6,16 +6,19 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.location.impl.show import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.location.api.Location import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState @@ -26,115 +29,111 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShowLocationViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `test back action`() { + fun `test back action`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setShowLocationView( + setShowLocationView( state = aShowLocationState( eventSink = eventsRecorder ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `test share action`() { + fun `test share action`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - val shareContentDescription = rule.activity.getString(CommonStrings.action_share) - rule.onNodeWithContentDescription(shareContentDescription).performClick() + val shareContentDescription = activity!!.getString(CommonStrings.action_share) + onNodeWithContentDescription(shareContentDescription).performClick() // The default aStaticLocationMode uses Location(1.23, 2.34, 4f) eventsRecorder.assertSingle(ShowLocationEvent.Share(Location(1.23, 2.34, 4f))) } @Test - fun `test fab click`() { + fun `test fab click`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick() + onNodeWithTag(TestTags.floatingActionButton.value).performClick() eventsRecorder.assertSingle(ShowLocationEvent.TrackMyLocation(true)) } @Test - fun `when permission denied is displayed user can open the settings`() { + fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( constraintsDialogState = LocationConstraintsDialogState.PermissionDenied, eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ShowLocationEvent.OpenAppSettings) } @Test - fun `when permission denied is displayed user can close the dialog`() { + fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( constraintsDialogState = LocationConstraintsDialogState.PermissionDenied, eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog) } @Test - fun `when permission rationale is displayed user can request permissions`() { + fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( constraintsDialogState = LocationConstraintsDialogState.PermissionRationale, eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ShowLocationEvent.RequestPermissions) } @Test - fun `when permission rationale is displayed user can close the dialog`() { + fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setShowLocationView( + setShowLocationView( aShowLocationState( constraintsDialogState = LocationConstraintsDialogState.PermissionRationale, eventSink = eventsRecorder ), onBackClick = EnsureNeverCalled(), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog) } } -private fun AndroidComposeTestRule.setShowLocationView( +private fun AndroidComposeUiTest.setShowLocationView( state: ShowLocationState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt index 1ecb79bd67..e6d1659778 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt @@ -6,60 +6,57 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.lockscreen.impl.unlock.keypad import android.view.KeyEvent import androidx.activity.ComponentActivity import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isRoot -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performKeyInput import androidx.compose.ui.test.pressKey import androidx.compose.ui.test.requestFocus +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.compose.ui.unit.dp import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class PinKeypadTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on a number emits the expected event`() { + fun `clicking on a number emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPinKeyPad(onClick = eventsRecorder) - rule.onNode(hasText("1")).performClick() + setPinKeyPad(onClick = eventsRecorder) + onNode(hasText("1")).performClick() eventsRecorder.assertSingle(PinKeypadModel.Number('1')) } @Test - fun `clicking on the delete previous character button emits the expected event`() { + fun `clicking on the delete previous character button emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPinKeyPad(onClick = eventsRecorder) - rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.a11y_delete))).performClick() + setPinKeyPad(onClick = eventsRecorder) + onNode(hasContentDescription(activity!!.getString(CommonStrings.a11y_delete))).performClick() eventsRecorder.assertSingle(PinKeypadModel.Back) } @OptIn(ExperimentalTestApi::class) @Test - fun `typing using the hardware keyboard emits the expected events`() { + fun `typing using the hardware keyboard emits the expected events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPinKeyPad(onClick = eventsRecorder) - rule.onNodeWithText("1").requestFocus() - rule.onAllNodes(isRoot())[0].performKeyInput { + setPinKeyPad(onClick = eventsRecorder) + onNodeWithText("1").requestFocus() + onAllNodes(isRoot())[0].performKeyInput { val keys = listOf( Key.A, Key.NumPad1, @@ -118,7 +115,7 @@ class PinKeypadTest { ) } - private fun AndroidComposeTestRule.setPinKeyPad( + private fun AndroidComposeUiTest.setPinKeyPad( onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt index c6610b212c..61ec7cc698 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.chooseaccountprovider import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.login.impl.accountprovider.anAccountProvider import io.element.android.libraries.architecture.AsyncData @@ -25,36 +28,31 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class ChooseAccountProviderViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setChooseAccountProviderView( + setChooseAccountProviderView( state = aChooseAccountProviderState( eventSink = eventSink, ), onBackClick = it, ) - rule.pressBack() + pressBack() } } @Config(qualifiers = "h1024dp") @Test - fun `selecting an account provider emits the the expected event`() { + fun `selecting an account provider emits the the expected event`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() - rule.setChooseAccountProviderView( + setChooseAccountProviderView( state = aChooseAccountProviderState( accountProviders = listOf( ChooseAccountProviderPresenterTest.accountProvider1, @@ -64,24 +62,24 @@ class ChooseAccountProviderViewTest { eventSink = eventSink, ), ) - rule.onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick() + onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick() eventSink.assertSingle(ChooseAccountProviderEvents.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1)) } @Test - fun `when error is displayed - closing the dialog emits the expected event`() { + fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() - rule.setChooseAccountProviderView( + setChooseAccountProviderView( state = aChooseAccountProviderState( loginMode = AsyncData.Failure(AN_EXCEPTION), eventSink = eventSink, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventSink.assertSingle(ChooseAccountProviderEvents.ClearError) } - private fun AndroidComposeTestRule.setChooseAccountProviderView( + private fun AndroidComposeUiTest.setChooseAccountProviderView( state: ChooseAccountProviderState, onBackClick: () -> Unit = EnsureNeverCalled(), onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt index 26da50da63..c0e7e5c378 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt @@ -6,20 +6,23 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.loginpassword import androidx.activity.ComponentActivity +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.test.A_PASSWORD import io.element.android.libraries.matrix.test.A_USER_NAME @@ -30,158 +33,154 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class LoginPasswordViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke back callback`() { + fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `changing login invokes the expected event`() { + fun `changing login invokes the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, ), ) - val userNameHint = rule.activity.getString(CommonStrings.common_username) - rule.onNodeWithText(userNameHint).performTextInput(A_USER_NAME) + val userNameHint = activity!!.getString(CommonStrings.common_username) + onNodeWithText(userNameHint).performTextInput(A_USER_NAME) eventsRecorder.assertSingle( LoginPasswordEvents.SetLogin(A_USER_NAME) ) } @Test - fun `changing login removes new lines the expected event`() { + fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, ), ) - val userNameHint = rule.activity.getString(CommonStrings.common_username) - rule.onNodeWithText(userNameHint).performTextInput("a\nb") + val userNameHint = activity!!.getString(CommonStrings.common_username) + onNodeWithText(userNameHint).performTextInput("a\nb") eventsRecorder.assertSingle( LoginPasswordEvents.SetLogin("ab") ) } @Test - fun `clearing login invokes the expected event`() { + fun `clearing login invokes the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(A_USER_NAME), eventSink = eventsRecorder, ), ) - val a11yClear = rule.activity.getString(CommonStrings.action_clear) - rule.onNodeWithContentDescription(a11yClear).performClick() + val a11yClear = activity!!.getString(CommonStrings.action_clear) + onNodeWithContentDescription(a11yClear).performClick() eventsRecorder.assertSingle( LoginPasswordEvents.SetLogin("") ) } @Test - fun `changing password invokes the expected event`() { + fun `changing password invokes the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, ), ) - val userNameHint = rule.activity.getString(CommonStrings.common_password) - rule.onNodeWithText(userNameHint).performTextInput(A_PASSWORD) + val userNameHint = activity!!.getString(CommonStrings.common_password) + onNodeWithText(userNameHint).performTextInput(A_PASSWORD) eventsRecorder.assertSingle( LoginPasswordEvents.SetPassword(A_PASSWORD) ) } @Test - fun `reveal password makes the password visible`() { + fun `reveal password makes the password visible`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(password = A_PASSWORD), eventSink = eventsRecorder, ), ) - rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••")) + onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••")) + val resources = activity!!.resources // Show password - val a11yShowPassword = rule.activity.getString(CommonStrings.a11y_show_password) - rule.onNodeWithContentDescription(a11yShowPassword).performClick() - rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD)) + val a11yShowPassword = resources.getString(CommonStrings.a11y_show_password) + onNodeWithContentDescription(a11yShowPassword).performClick() + onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD)) // Hide password - val a11yHidePassword = rule.activity.getString(CommonStrings.a11y_hide_password) - rule.onNodeWithContentDescription(a11yHidePassword).performClick() - rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••")) + val a11yHidePassword = resources.getString(CommonStrings.a11y_hide_password) + onNodeWithContentDescription(a11yHidePassword).performClick() + onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••")) } @Test - fun `when login is empty, continue button is not enabled`() { + fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(password = A_PASSWORD), eventSink = eventsRecorder, ), ) - val continueStr = rule.activity.getString(CommonStrings.action_continue) - rule.onNodeWithText(continueStr).assertIsNotEnabled() + val continueStr = activity!!.getString(CommonStrings.action_continue) + onNodeWithText(continueStr).assertIsNotEnabled() } @Test - fun `when password is empty, continue button is not enabled`() { + fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(login = A_USER_NAME), eventSink = eventsRecorder, ), ) - val continueStr = rule.activity.getString(CommonStrings.action_continue) - rule.onNodeWithText(continueStr).assertIsNotEnabled() + val continueStr = activity!!.getString(CommonStrings.action_continue) + onNodeWithText(continueStr).assertIsNotEnabled() } @Config(qualifiers = "h1024dp") @Test - fun `clicking on Continue sends expected event`() { + fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLoginPasswordView( + setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD), eventSink = eventsRecorder, ), ) - val continueStr = rule.activity.getString(CommonStrings.action_continue) - rule.onNodeWithText(continueStr).assertIsEnabled() - rule.clickOn(CommonStrings.action_continue) + val continueStr = activity!!.getString(CommonStrings.action_continue) + onNodeWithText(continueStr).assertIsEnabled() + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle( LoginPasswordEvents.Submit ) } } -private fun AndroidComposeTestRule.setLoginPasswordView( +private fun AndroidComposeUiTest.setLoginPasswordView( state: LoginPasswordState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt index a8f0ccbb5a..bcb62ea707 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.onboarding import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import com.google.testing.junit.testparameterinjector.KotlinTestParameters.namedTestValues import com.google.testing.junit.testparameterinjector.TestParameter import io.element.android.features.login.impl.R @@ -29,22 +32,17 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.RobolectricTestParameterInjector @RunWith(RobolectricTestParameterInjector::class) class OnboardingViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `when can create account - clicking on create account calls the expected callback`() { + fun `when can create account - clicking on create account calls the expected callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canCreateAccount = true, showDeveloperSettings = false, @@ -52,40 +50,40 @@ class OnboardingViewTest { ), onCreateAccount = callback, ) - rule.clickOn(R.string.screen_onboarding_sign_up) + clickOn(R.string.screen_onboarding_sign_up) // Developer settings should not be shown - val developerSettingsText = rule.activity.getString(CommonStrings.common_developer_options) - rule.onNodeWithContentDescription(developerSettingsText).assertDoesNotExist() + val developerSettingsText = activity!!.getString(CommonStrings.common_developer_options) + onNodeWithContentDescription(developerSettingsText).assertDoesNotExist() } } @Test - fun `when can go back - clicking on back calls the expected callback`() { + fun `when can go back - clicking on back calls the expected callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( isAddingAccount = true, eventSink = eventSink, ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() { + fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canLoginWithQrCode = true, eventSink = eventSink, ), onSignInWithQrCode = callback, ) - rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code) + clickOn(R.string.screen_onboarding_sign_in_with_qr_code) } } @@ -95,10 +93,10 @@ class OnboardingViewTest { "can search account provider" to false, "cannot search account provider" to true, ) - ) { + ) = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canLoginWithQrCode = true, mustChooseAccountProvider = mustChooseAccountProvider, @@ -106,7 +104,7 @@ class OnboardingViewTest { ), onSignIn = callback, ) - rule.clickOn(R.string.screen_onboarding_sign_in_manually) + clickOn(R.string.screen_onboarding_sign_in_manually) } } @@ -116,10 +114,10 @@ class OnboardingViewTest { "can search account provider" to false, "cannot search account provider" to true, ) - ) { + ) = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canLoginWithQrCode = false, canCreateAccount = false, @@ -128,89 +126,89 @@ class OnboardingViewTest { ), onSignIn = callback, ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) } } @Test - fun `when sign in to pre defined account provider - clicking on button emits the expected event`() { + fun `when sign in to pre defined account provider - clicking on button emits the expected event`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( defaultAccountProvider = "element.io", eventSink = eventSink, ), ) - val buttonText = rule.activity.getString(R.string.screen_onboarding_sign_in_to, "element.io") - rule.onNodeWithText(buttonText).performClick() + val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io") + onNodeWithText(buttonText).performClick() eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io")) } @Test - fun `when error is displayed - closing the dialog emits the expected event`() { + fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( defaultAccountProvider = "element.io", loginMode = AsyncData.Failure(AN_EXCEPTION), eventSink = eventSink, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventSink.assertSingle(OnBoardingEvents.ClearError) } @Test - fun `clicking on report a problem calls the sign in callback`() { + fun `clicking on report a problem calls the sign in callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canReportBug = true, eventSink = eventSink, ), onReportProblem = callback, ) - val text = rule.activity.getString(CommonStrings.common_report_a_problem) - rule.onNodeWithText(text).assertExists() - rule.clickOn(CommonStrings.common_report_a_problem) + val text = activity!!.getString(CommonStrings.common_report_a_problem) + onNodeWithText(text).assertExists() + clickOn(CommonStrings.common_report_a_problem) } } @Test - fun `clicking on settings calls the developer settings callback`() { + fun `clicking on settings calls the developer settings callback`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( showDeveloperSettings = true, eventSink = eventSink, ), onDeveloperSettingsClick = callback, ) - val text = rule.activity.getString(CommonStrings.common_developer_options) - rule.onNodeWithContentDescription(text).performClick() + val text = activity!!.getString(CommonStrings.common_developer_options) + onNodeWithContentDescription(text).performClick() } } @Test - fun `cannot report a problem when the feature is disabled`() { + fun `cannot report a problem when the feature is disabled`() = runAndroidComposeUiTest { val eventSink = EventsRecorder(expectEvents = false) - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( canReportBug = false, eventSink = eventSink, ), ) - val text = rule.activity.getString(CommonStrings.common_report_a_problem) - rule.onNodeWithText(text).assertDoesNotExist() + val text = activity!!.getString(CommonStrings.common_report_a_problem) + onNodeWithText(text).assertDoesNotExist() } @Test - fun `when success PasswordLogin - the expected callback is invoked and the event is received`() { + fun `when success PasswordLogin - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() ensureCalledOnce { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( loginMode = AsyncData.Success(LoginMode.PasswordLogin), eventSink = eventSink, @@ -222,11 +220,11 @@ class OnboardingViewTest { } @Test - fun `when success Oidc - the expected callback is invoked and the event is received`() { + fun `when success Oidc - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() val oAuthDetails = OAuthDetails("aUrl") ensureCalledOnceWithParam(oAuthDetails) { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( loginMode = AsyncData.Success(LoginMode.OAuth(oAuthDetails)), eventSink = eventSink, @@ -238,11 +236,11 @@ class OnboardingViewTest { } @Test - fun `when success AccountCreation - the expected callback is invoked and the event is received`() { + fun `when success AccountCreation - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { val eventSink = EventsRecorder() val oAuthDetails = OAuthDetails("aUrl") ensureCalledOnceWithParam(oAuthDetails.url) { callback -> - rule.setOnboardingView( + setOnboardingView( state = anOnBoardingState( loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl")), eventSink = eventSink, @@ -253,7 +251,7 @@ class OnboardingViewTest { eventSink.assertSingle(OnBoardingEvents.ClearError) } - private fun AndroidComposeTestRule.setOnboardingView( + private fun AndroidComposeUiTest.setOnboardingView( state: OnBoardingState, onBackClick: () -> Unit = EnsureNeverCalled(), onDeveloperSettingsClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt index a0469a684e..79566625c5 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt @@ -6,49 +6,47 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.qrcode.confirmation import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QrCodeConfirmationViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeConfirmationView( + setQrCodeConfirmationView( step = QrCodeConfirmationStep.DisplayCheckCode("12"), onCancel = callback ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on Cancel button clicked - calls the expected callback`() { + fun `on Cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeConfirmationView( + setQrCodeConfirmationView( step = QrCodeConfirmationStep.DisplayVerificationCode("123456"), onCancel = callback ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) } } - private fun AndroidComposeTestRule.setQrCodeConfirmationView( + private fun AndroidComposeUiTest.setQrCodeConfirmationView( step: QrCodeConfirmationStep, onCancel: () -> Unit ) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt index de0f689220..2ae68c3485 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.qrcode.error import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType import io.element.android.libraries.ui.strings.CommonStrings @@ -18,47 +21,42 @@ import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QrCodeErrorViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the onCancel callback`() { + fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeErrorView( + setQrCodeErrorView( onCancel = callback, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on try again button clicked - calls the expected callback`() { + fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeErrorView( + setQrCodeErrorView( onRetry = callback, ) - rule.clickOn(CommonStrings.action_try_again) + clickOn(CommonStrings.action_try_again) } } @Test - fun `on cancel button clicked - calls the expected callback`() { + fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeErrorView( + setQrCodeErrorView( onCancel = callback, ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) } } - private fun AndroidComposeTestRule.setQrCodeErrorView( + private fun AndroidComposeUiTest.setQrCodeErrorView( onRetry: () -> Unit = EnsureNeverCalled(), onCancel: () -> Unit = EnsureNeverCalled(), errorScreenType: QrCodeErrorScreenType = QrCodeErrorScreenType.UnknownError, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt index cec67e5011..c6812d3759 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.qrcode.intro import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.login.impl.R import io.element.android.tests.testutils.EnsureNeverCalled @@ -19,42 +22,37 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QrCodeIntroViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeIntroView( + setQrCodeIntroView( state = aQrCodeIntroState(), onBackClicked = callback ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on back button clicked - calls the expected callback`() { + fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeIntroView( + setQrCodeIntroView( state = aQrCodeIntroState(), onBackClicked = callback ) - rule.pressBack() + pressBack() } } @Test - fun `when can continue - calls the expected callback`() { + fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeIntroView( + setQrCodeIntroView( state = aQrCodeIntroState(canContinue = true), onContinue = callback ) @@ -62,16 +60,16 @@ class QrCodeIntroViewTest { } @Test - fun `on submit button clicked - emits the Continue event`() { + fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest { val eventRecorder = EventsRecorder() - rule.setQrCodeIntroView( + setQrCodeIntroView( state = aQrCodeIntroState(eventSink = eventRecorder), ) - rule.clickOn(R.string.screen_qr_code_login_initial_state_button_title) + clickOn(R.string.screen_qr_code_login_initial_state_button_title) eventRecorder.assertSingle(QrCodeIntroEvents.Continue) } - private fun AndroidComposeTestRule.setQrCodeIntroView( + private fun AndroidComposeUiTest.setQrCodeIntroView( state: QrCodeIntroState, onBackClicked: () -> Unit = EnsureNeverCalled(), onContinue: () -> Unit = EnsureNeverCalled(), diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt index b8becd545f..bde960ef1a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.login.impl.screens.qrcode.scan import androidx.activity.ComponentActivity import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.element.android.libraries.architecture.AsyncAction @@ -24,16 +27,11 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBackKey import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QrCodeScanViewTest { - @get:Rule - val rule = createAndroidComposeRule() - private var provider: ProcessCameraProvider? = null @Before @@ -48,28 +46,28 @@ class QrCodeScanViewTest { } @Test - fun `on back pressed - calls the expected callback`() { + fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setQrCodeScanView( + setQrCodeScanView( state = aQrCodeScanState(), onBackClick = callback ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `on QR code data ready - calls the expected callback`() { + fun `on QR code data ready - calls the expected callback`() = runAndroidComposeUiTest { val data = FakeMatrixQrCodeLoginData() ensureCalledOnceWithParam(data) { callback -> - rule.setQrCodeScanView( + setQrCodeScanView( state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)), onQrCodeDataReady = callback ) } } - private fun AndroidComposeTestRule.setQrCodeScanView( + private fun AndroidComposeUiTest.setQrCodeScanView( state: QrCodeScanState, onBackClick: () -> Unit = EnsureNeverCalled(), onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt index 84ca038d7b..a42fd891d4 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.logout.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.testtags.TestTags @@ -21,97 +24,93 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressTag -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LogoutViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on logout sends a LogoutEvents`() { + fun `clicking on logout sends a LogoutEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + setLogoutView( aLogoutState( eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_signout) + clickOn(CommonStrings.action_signout) eventsRecorder.assertSingle(LogoutEvents.Logout(false)) } @Test - fun `confirming logout sends a LogoutEvents`() { + fun `confirming logout sends a LogoutEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + setLogoutView( aLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(LogoutEvents.Logout(false)) } @Test - fun `clicking on back invoke back callback`() { + fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLogoutView( + setLogoutView( aLogoutState( eventSink = eventsRecorder ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on confirm after error sends a LogoutEvents`() { + fun `clicking on confirm after error sends a LogoutEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + setLogoutView( aLogoutState( logoutAction = AsyncAction.Failure(Exception("Failed to logout")), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_signout_anyway) + clickOn(CommonStrings.action_signout_anyway) eventsRecorder.assertSingle(LogoutEvents.Logout(true)) } @Test - fun `clicking on cancel after error sends a LogoutEvents`() { + fun `clicking on cancel after error sends a LogoutEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLogoutView( + setLogoutView( aLogoutState( logoutAction = AsyncAction.Failure(Exception("Failed to logout")), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(LogoutEvents.CloseDialogs) } @Test - fun `last session setting button invoke onChangeRecoveryKeyClicked`() { + fun `last session setting button invoke onChangeRecoveryKeyClicked`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setLogoutView( + setLogoutView( aLogoutState( isLastDevice = true, eventSink = eventsRecorder ), onChangeRecoveryKeyClick = callback, ) - rule.clickOn(CommonStrings.common_settings) + clickOn(CommonStrings.common_settings) } } } -private fun AndroidComposeTestRule.setLogoutView( +private fun AndroidComposeUiTest.setLogoutView( state: LogoutState, onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt index 8eae534740..99860259c4 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.logout.impl.direct import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutState @@ -21,83 +24,79 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBackKey import org.junit.Ignore -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DefaultDirectLogoutViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on confirm logout sends expected Event`() { + fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDefaultDirectLogoutView( + setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_signout) + clickOn(CommonStrings.action_signout) eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false)) } @Test - fun `clicking on cancel logout sends expected Event`() { + fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDefaultDirectLogoutView( + setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) } @Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.") @Test - fun `clicking on back invoke back callback`() { + fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDefaultDirectLogoutView( + setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder, ) ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) } @Test - fun `clicking on confirm after error sends expected Event`() { + fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDefaultDirectLogoutView( + setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.Failure(Exception("Error")), eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_signout_anyway) + clickOn(CommonStrings.action_signout_anyway) eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true)) } @Test - fun `clicking on cancel after error sends expected Event`() { + fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDefaultDirectLogoutView( + setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.Failure(Exception("Error")), eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) } } -private fun AndroidComposeTestRule.setDefaultDirectLogoutView( +private fun AndroidComposeUiTest.setDefaultDirectLogoutView( state: DirectLogoutState, ) { setContent { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 62b9eac68d..70ef70325e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -6,13 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.longClick import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithTag @@ -25,6 +27,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeRight +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.emojibasebindings.Emoji @@ -78,82 +81,78 @@ import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.setSafeContent import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config import kotlin.time.Duration.Companion.milliseconds @RunWith(AndroidJUnit4::class) class MessagesViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke expected callback`() { + fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMessagesView( + setMessagesView( state = state, onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on room name invoke expected callback`() { + fun `clicking on room name invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMessagesView( + setMessagesView( state = state, onRoomDetailsClick = callback, ) - rule.onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick() + onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick() } } @Test - fun `clicking on join call invoke expected callback`() { + fun `clicking on join call invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) ensureCalledOnceWithParam(false) { callback -> - rule.setMessagesView( + setMessagesView( state = state, onJoinCallClick = callback, ) - val joinCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_call) - rule.onNodeWithContentDescription(joinCallContentDescription).performClick() + val joinCallContentDescription = activity!!.getString(CommonStrings.a11y_start_call) + onNodeWithContentDescription(joinCallContentDescription).performClick() } } @Test - fun `clicking on join voice call invoke expected callback`() { + fun `clicking on join voice call invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder, roomCallState = aStandByCallState(isDM = true) ) ensureCalledOnceWithParam(true) { callback -> - rule.setMessagesView( + setMessagesView( state = state, onJoinCallClick = callback, ) - val joinVoiceCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_voice_call) - rule.onNodeWithContentDescription(joinVoiceCallContentDescription).performClick() + val joinVoiceCallContentDescription = activity!!.getString(CommonStrings.a11y_start_voice_call) + onNodeWithContentDescription(joinVoiceCallContentDescription).performClick() } } @Test - fun `clicking on an Event invoke expected callback`() { + fun `clicking on an Event invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( timelineState = aTimelineState( @@ -167,12 +166,12 @@ class MessagesViewTest { expectedParam2 = timelineItem, result = true, ) - rule.setMessagesView( + setMessagesView( state = state, onEventClick = callback, ) // Cannot perform click on "Text", it's not detected. Use tag instead - rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick() + onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick() callback.assertSuccess() } @@ -202,7 +201,7 @@ class MessagesViewTest { userHasPermissionToRedactOther: Boolean = false, userHasPermissionToSendReaction: Boolean = false, userCanPinEvent: Boolean = false, - ) { + ) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( actionListState = anActionListState( @@ -220,11 +219,11 @@ class MessagesViewTest { ), ) val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event - rule.setMessagesView( + setMessagesView( state = state, ) // Cannot perform click on "Text", it's not detected. Use tag instead - rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() } + onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() } eventsRecorder.assertSingle( ActionListEvent.ComputeForMessage( event = timelineItem, @@ -235,7 +234,7 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") - fun `clicking on a read receipt list emits the expected Event`() { + fun `clicking on a read receipt list emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( @@ -255,10 +254,10 @@ class MessagesViewTest { ), ) val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event - rule.setMessagesView( + setMessagesView( state = state, ) - rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick() + onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem)) } @@ -272,7 +271,7 @@ class MessagesViewTest { swipeTest(userHasPermissionToSendMessage = false) } - private fun swipeTest(userHasPermissionToSendMessage: Boolean) { + private fun swipeTest(userHasPermissionToSendMessage: Boolean) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true) val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false) @@ -285,10 +284,10 @@ class MessagesViewTest { ), eventSink = eventsRecorder, ) - rule.setMessagesView( + setMessagesView( state = state, ) - rule.onAllNodesWithTag(TestTags.messageBubble.value).apply { + onAllNodesWithTag(TestTags.messageBubble.value).apply { onFirst().performTouchInput { swipeRight(endX = 200f) } onLast().performTouchInput { swipeRight(endX = 200f) } } @@ -300,7 +299,7 @@ class MessagesViewTest { } @Test - fun `clicking on send location invoke expected callback`() { + fun `clicking on send location invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( composerState = aMessageComposerState( @@ -309,16 +308,16 @@ class MessagesViewTest { eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMessagesView( + setMessagesView( state = state, onSendLocationClick = callback, ) - rule.clickOn(R.string.screen_room_attachment_source_location) + clickOn(R.string.screen_room_attachment_source_location) } } @Test - fun `clicking on create poll invoke expected callback`() { + fun `clicking on create poll invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( composerState = aMessageComposerState( @@ -327,25 +326,25 @@ class MessagesViewTest { eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMessagesView( + setMessagesView( state = state, onCreatePollClick = callback, ) // Then click on the poll action - rule.clickOn(R.string.screen_room_attachment_source_poll) + clickOn(R.string.screen_room_attachment_source_poll) } } @Test @Config(qualifiers = "h1024dp") - fun `clicking on the avatar of the sender of an Event emits the expected event`() { + fun `clicking on the avatar of the sender of an Event emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder ) val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first() - rule.setMessagesView(state = state) - rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() + setMessagesView(state = state) + onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle( MessagesEvent.OnUserClicked( MatrixUser( @@ -359,12 +358,12 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") - fun `clicking on the display name of the sender of an Event emits expected event`() { + fun `clicking on the display name of the sender of an Event emits expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState(eventSink = eventsRecorder) val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first() - rule.setMessagesView(state = state) - rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() + setMessagesView(state = state) + onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle( MessagesEvent.OnUserClicked( MatrixUser( @@ -377,7 +376,7 @@ class MessagesViewTest { } @Test - fun `selecting a action on a message emits the expected Event`() { + fun `selecting a action on a message emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder @@ -395,17 +394,17 @@ class MessagesViewTest { ) ), ) - rule.setMessagesView( + setMessagesView( state = stateWithMessageAction, ) - rule.clickOn(CommonStrings.action_edit) + clickOn(CommonStrings.action_edit) // Give time for the close animation to complete - rule.mainClock.advanceTimeBy(milliseconds = 1_000) + mainClock.advanceTimeBy(milliseconds = 1_000) eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem)) } @Test - fun `clicking on a reaction emits the expected Event`() { + fun `clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( @@ -414,10 +413,10 @@ class MessagesViewTest { eventSink = eventsRecorder, ) val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event - rule.setMessagesView( + setMessagesView( state = state, ) - rule.onAllNodesWithText( + onAllNodesWithText( text = "👍️", useUnmergedTree = true, ).onFirst().performClick() @@ -425,7 +424,7 @@ class MessagesViewTest { } @Test - fun `long clicking on a reaction emits the expected Event`() { + fun `long clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( @@ -437,10 +436,10 @@ class MessagesViewTest { ), ) val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event - rule.setMessagesView( + setMessagesView( state = state, ) - rule.onAllNodesWithText( + onAllNodesWithText( text = "👍️", useUnmergedTree = true, ).onFirst().performTouchInput { longClick() } @@ -448,7 +447,7 @@ class MessagesViewTest { } @Test - fun `clicking on more reaction emits the expected Event`() { + fun `clicking on more reaction emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( @@ -459,16 +458,16 @@ class MessagesViewTest { ), ) val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event - rule.setMessagesView( + setMessagesView( state = state, ) - val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction) - rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick() + val moreReactionContentDescription = activity!!.getString(R.string.screen_room_timeline_add_reaction) + onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick() eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem)) } @Test - fun `clicking on more reaction from action list emits the expected Event`() { + fun `clicking on more reaction from action list emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( @@ -491,18 +490,18 @@ class MessagesViewTest { eventSink = eventsRecorder ), ) - rule.setMessagesView( + setMessagesView( state = stateWithActionListState, ) - val moreReactionContentDescription = rule.activity.getString(CommonStrings.a11y_react_with_other_emojis) - rule.onNodeWithContentDescription(moreReactionContentDescription).performClick() + val moreReactionContentDescription = activity!!.getString(CommonStrings.a11y_react_with_other_emojis) + onNodeWithContentDescription(moreReactionContentDescription).performClick() // Give time for the close animation to complete - rule.mainClock.advanceTimeBy(milliseconds = 1_000) + mainClock.advanceTimeBy(milliseconds = 1_000) eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem)) } @Test - fun `clicking on verified user send failure from action list emits the expected Event`() { + fun `clicking on verified user send failure from action list emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState() val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event @@ -519,21 +518,21 @@ class MessagesViewTest { ), timelineState = aTimelineState(eventSink = eventsRecorder) ) - rule.setMessagesView( + setMessagesView( state = stateWithActionListState, ) // Clear initial 'LoadMore' event emitted when setting the state eventsRecorder.clear() - val verifiedUserSendFailure = rule.activity.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice") - rule.onNodeWithText(verifiedUserSendFailure).performClick() + val verifiedUserSendFailure = activity!!.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice") + onNodeWithText(verifiedUserSendFailure).performClick() // Give time for the close animation to complete - rule.mainClock.advanceTimeBy(milliseconds = 1_000) + mainClock.advanceTimeBy(milliseconds = 1_000) eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem)) } @Test - fun `clicking on a custom emoji emits the expected Events`() { + fun `clicking on a custom emoji emits the expected Events`() = runAndroidComposeUiTest { val aUnicode = "🙈" val customReactionStateEventsRecorder = EventsRecorder() val eventsRecorder = EventsRecorder() @@ -563,18 +562,18 @@ class MessagesViewTest { eventSink = customReactionStateEventsRecorder ), ) - rule.setMessagesView( + setMessagesView( state = stateWithCustomReactionState, ) - rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick() + onNodeWithText(aUnicode, useUnmergedTree = true).performClick() // Give time for the close animation to complete - rule.mainClock.advanceTimeBy(milliseconds = 1_000) + mainClock.advanceTimeBy(milliseconds = 1_000) customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet) eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId)) } @Test - fun `clicking on pinned messages banner emits the expected Event`() { + fun `clicking on pinned messages banner emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState(eventSink = eventsRecorder), @@ -587,16 +586,16 @@ class MessagesViewTest { ), ), ) - rule.setMessagesView(state = state) + setMessagesView(state = state) // Clear initial 'LoadMore' event emitted when setting the state eventsRecorder.clear() - rule.onNodeWithText("This is a pinned message").performClick() + onNodeWithText("This is a pinned message").performClick() eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)) } @Test - fun `clicking on successor room button emits expected event`() { + fun `clicking on successor room button emits expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val successorRoomId = RoomId("!successor:server.org") val state = aMessagesState( @@ -606,18 +605,18 @@ class MessagesViewTest { ), timelineState = aTimelineState(eventSink = eventsRecorder) ) - rule.setMessagesView(state = state) + setMessagesView(state = state) // Clear initial 'LoadMore' event emitted when setting the state eventsRecorder.clear() - val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action) + val text = activity!!.getString(R.string.screen_room_timeline_tombstoned_room_action) // The bottomsheet subcompose seems to make the node to appear twice - rule.onAllNodesWithText(text).onFirst().performClick() + onAllNodesWithText(text).onFirst().performClick() eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId)) } @Test - fun `clicking on threads list button calls the expected function`() { + fun `clicking on threads list button calls the expected function`() = runAndroidComposeUiTest { val state = aMessagesState( threads = MessagesState.Threads( hasThreads = true, @@ -625,28 +624,28 @@ class MessagesViewTest { ) ) val onThreadsListClicked = lambdaRecorder {} - rule.setMessagesView( + setMessagesView( state = state, onThreadsListClicked = onThreadsListClicked, ) - rule.onNodeWithContentDescription("Threads").performClick() + onNodeWithContentDescription("Threads").performClick() onThreadsListClicked.assertions().isCalledOnce() } @Test - fun `no banner shown when there is no successor room`() { + fun `no banner shown when there is no successor room`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( successorRoom = null, eventSink = eventsRecorder ) - rule.setMessagesView(state = state) - rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message) - rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action) + setMessagesView(state = state) + assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message) + assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action) } } -private fun AndroidComposeTestRule.setMessagesView( +private fun AndroidComposeUiTest.setMessagesView( state: MessagesState, onBackClick: () -> Unit = EnsureNeverCalled(), onRoomDetailsClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt index 24779ba78a..0ee342513a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.crypto.identity import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.designsystem.components.avatar.anAvatarData import io.element.android.libraries.matrix.api.core.UserId @@ -21,19 +24,15 @@ import io.element.android.libraries.matrix.ui.room.RoomMemberIdentityStateChange import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class IdentityChangeStateViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `show and resolve pin violation`() { + fun `show and resolve pin violation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIdentityChangeStateView( + setIdentityChangeStateView( state = anIdentityChangeState( listOf( RoomMemberIdentityStateChange( @@ -45,18 +44,18 @@ class IdentityChangeStateViewTest { ), ) - rule.onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning") - rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") - rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") + onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning") + onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") + onNodeWithText("Alice", substring = true).assertExists("should display user displayname") - rule.clickOn(res = CommonStrings.action_dismiss) + clickOn(res = CommonStrings.action_dismiss) eventsRecorder.assertSingle(IdentityChangeEvent.PinIdentity(UserId("@alice:localhost"))) } @Test - fun `show and resolve verification violation`() { + fun `show and resolve verification violation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIdentityChangeStateView( + setIdentityChangeStateView( state = anIdentityChangeState( listOf( RoomMemberIdentityStateChange( @@ -68,17 +67,17 @@ class IdentityChangeStateViewTest { ), ) - rule.onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning") - rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") - rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") + onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning") + onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") + onNodeWithText("Alice", substring = true).assertExists("should display user displayname") - rule.clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action) + clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action) eventsRecorder.assertSingle(IdentityChangeEvent.WithdrawVerification(UserId("@alice:localhost"))) } @Test - fun `Should not show any banner if no violations`() { - rule.setIdentityChangeStateView( + fun `Should not show any banner if no violations`() = runAndroidComposeUiTest { + setIdentityChangeStateView( state = anIdentityChangeState( listOf( RoomMemberIdentityStateChange( @@ -93,10 +92,10 @@ class IdentityChangeStateViewTest { ), ) - rule.onNodeWithText("identity was reset", substring = true).assertDoesNotExist() + onNodeWithText("identity was reset", substring = true).assertDoesNotExist() } - private fun AndroidComposeTestRule.setIdentityChangeStateView( + private fun AndroidComposeUiTest.setIdentityChangeStateView( state: IdentityChangeState, ) { setContent { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt index 02767fbeb9..07a0fd5f94 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt @@ -6,54 +6,53 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.crypto.sendfailure.resolve import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ResolveVerifiedUserSendFailureViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on resolve and resend emit the expected event`() { + fun `clicking on resolve and resend emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResolveVerifiedUserSendFailureView( + setResolveVerifiedUserSendFailureView( state = aResolveVerifiedUserSendFailureState( verifiedUserSendFailure = aChangedIdentitySendFailure(), eventSink = eventsRecorder, ), ) - rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title) + clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title) eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } @Test - fun `clicking on retry emit the expected event`() { + fun `clicking on retry emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResolveVerifiedUserSendFailureView( + setResolveVerifiedUserSendFailureView( state = aResolveVerifiedUserSendFailureState( verifiedUserSendFailure = aChangedIdentitySendFailure(), eventSink = eventsRecorder, ), ) - rule.clickOn(res = CommonStrings.action_retry) + clickOn(res = CommonStrings.action_retry) eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry) } - private fun AndroidComposeTestRule.setResolveVerifiedUserSendFailureView( + private fun AndroidComposeUiTest.setResolveVerifiedUserSendFailureView( state: ResolveVerifiedUserSendFailureState, ) { setSafeContent { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt index e198ea9043..b656430466 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.link import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.ui.strings.CommonStrings @@ -19,51 +22,46 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.wysiwyg.link.Link -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LinkViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on cancel emits the expected event`() { + fun `clicking on cancel emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLinkView( + setLinkView( aLinkState( linkClick = ConfirmingLinkClick(aLink), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle( LinkEvent.Cancel ) } @Test - fun `clicking on continue emits the expected event`() { + fun `clicking on continue emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setLinkView( + setLinkView( aLinkState( linkClick = ConfirmingLinkClick(aLink), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle( LinkEvent.Confirm ) } @Test - fun `success state invokes the callback and emits the expected event`() { + fun `success state invokes the callback and emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(aLink) { callback -> - rule.setLinkView( + setLinkView( aLinkState( linkClick = AsyncAction.Success(aLink), eventSink = eventsRecorder, @@ -77,7 +75,7 @@ class LinkViewTest { } } -private fun AndroidComposeTestRule.setLinkView( +private fun AndroidComposeUiTest.setLinkView( state: LinkState, onLinkValid: (Link) -> Unit = EnsureNeverCalledWithParam(), ) { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt index 2c33e348c0..546731ff87 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.pinned.banner import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.ui.strings.CommonStrings @@ -22,49 +25,45 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PinnedMessagesBannerViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on the banner invoke expected callback`() { + fun `clicking on the banner invoke expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aLoadedPinnedMessagesBannerState( eventSink = eventsRecorder ) val pinnedEventId = state.currentPinnedMessage.eventId ensureCalledOnceWithParam(pinnedEventId) { callback -> - rule.setPinnedMessagesBannerView( + setPinnedMessagesBannerView( state = state, onClick = callback ) - rule.onRoot().performClick() + onRoot().performClick() eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned) } } @Test - fun `clicking on view all emit the expected event`() { + fun `clicking on view all emit the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = true) val state = aLoadedPinnedMessagesBannerState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setPinnedMessagesBannerView( + setPinnedMessagesBannerView( state = state, onViewAllClick = callback ) - rule.clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title) + clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title) } } } -private fun AndroidComposeTestRule.setPinnedMessagesBannerView( +private fun AndroidComposeUiTest.setPinnedMessagesBannerView( state: PinnedMessagesBannerState, onClick: (EventId) -> Unit = EnsureNeverCalledWithParam(), onViewAllClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index 41671b71c1..9c10abb631 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -6,16 +6,19 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.pinned.list import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.longClick import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.anActionListState @@ -31,33 +34,28 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.setSafeContent import io.element.android.wysiwyg.link.Link -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PinnedMessagesListViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back calls the expected callback`() { + fun `clicking on back calls the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aLoadedPinnedMessagesListState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setPinnedMessagesListView( + setPinnedMessagesListView( state = state, onBackClick = callback ) - rule.pressBack() + pressBack() } } @Test - fun `click on an event calls the expected callback`() { + fun `click on an event calls the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val content = aTimelineItemFileContent() val state = aLoadedPinnedMessagesListState( @@ -67,16 +65,16 @@ class PinnedMessagesListViewTest { val event = state.timelineItems.first() as TimelineItem.Event ensureCalledOnceWithParam(event) { callback -> - rule.setPinnedMessagesListView( + setPinnedMessagesListView( state = state, onEventClick = callback ) - rule.onAllNodesWithText(content.filename).onFirst().performClick() + onAllNodesWithText(content.filename).onFirst().performClick() } } @Test - fun `long click on an event emits the expected event`() { + fun `long click on an event emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = true) val content = aTimelineItemFileContent() val state = aLoadedPinnedMessagesListState( @@ -84,10 +82,10 @@ class PinnedMessagesListViewTest { actionListState = anActionListState(eventSink = eventsRecorder) ) - rule.setPinnedMessagesListView( + setPinnedMessagesListView( state = state, ) - rule.onAllNodesWithText(content.filename).onFirst() + onAllNodesWithText(content.filename).onFirst() .performTouchInput { longClick() } @@ -96,7 +94,7 @@ class PinnedMessagesListViewTest { } } -private fun AndroidComposeTestRule.setPinnedMessagesListView( +private fun AndroidComposeUiTest.setPinnedMessagesListView( state: PinnedMessagesListState, onBackClick: () -> Unit = EnsureNeverCalled(), onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index 315d9c459c..9e98f0fa49 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.timeline import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runComposeUiTest import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -18,15 +21,12 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DefaultHtmlConverterProviderTest { - @get:Rule val composeTestRule = createComposeRule() - private val provider = DefaultHtmlConverterProvider( mentionSpanProvider = MentionSpanProvider( permalinkParser = FakePermalinkParser(), @@ -43,8 +43,8 @@ class DefaultHtmlConverterProviderTest { } @Test - fun `calling provide after calling Update first should return an HtmlConverter`() { - composeTestRule.setContent { + fun `calling provide after calling Update first should return an HtmlConverter`() = runComposeUiTest { + setContent { CompositionLocalProvider(LocalInspectionMode provides true) { provider.Update() } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 3a0b0e1224..2138d4ced2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -6,15 +6,18 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.timeline import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToIndex +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.aCriticalShield @@ -39,19 +42,15 @@ import io.element.android.wysiwyg.link.Link import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import org.junit.Ignore -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class TimelineViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `reaching the end of the timeline with more events to load emits a LoadMore event`() { + fun `reaching the end of the timeline with more events to load emits a LoadMore event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf( TimelineItem.Virtual( @@ -66,9 +65,9 @@ class TimelineViewTest { } @Test - fun `reaching the end of the timeline does not send a LoadMore event`() { + fun `reaching the end of the timeline does not send a LoadMore event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())), eventSink = eventsRecorder, @@ -78,9 +77,9 @@ class TimelineViewTest { } @Test - fun `scroll to bottom on live timeline does not emit the Event`() { + fun `scroll to bottom on live timeline does not emit the Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())), isLive = true, @@ -92,14 +91,14 @@ class TimelineViewTest { eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0)) eventsRecorder.clear() - val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom) - rule.onNodeWithContentDescription(contentDescription).performClick() + val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom) + onNodeWithContentDescription(contentDescription).performClick() } @Test - fun `scroll to bottom on detached timeline emits the expected Event`() { + fun `scroll to bottom on detached timeline emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())), isLive = false, @@ -110,15 +109,15 @@ class TimelineViewTest { eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0)) eventsRecorder.clear() - val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom) - rule.onNodeWithContentDescription(contentDescription).performClick() + val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom) + onNodeWithContentDescription(contentDescription).performClick() eventsRecorder.assertSingle(TimelineEvent.JumpToLive) } @Test - fun `an empty timeline triggers a prefetch`() { + fun `an empty timeline triggers a prefetch`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf(), eventSink = eventsRecorder, @@ -129,9 +128,9 @@ class TimelineViewTest { } @Test - fun `show shield dialog`() { + fun `show shield dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf( aTimelineItemEvent( @@ -143,8 +142,8 @@ class TimelineViewTest { eventSink = eventsRecorder, ), ) - val contentDescription = rule.activity.getString(CommonStrings.a11y_encryption_details) - rule.onNodeWithContentDescription(contentDescription).performClick() + val contentDescription = activity!!.getString(CommonStrings.a11y_encryption_details) + onNodeWithContentDescription(contentDescription).performClick() eventsRecorder.assertList( listOf( TimelineEvent.OnScrollFinished(0), @@ -154,9 +153,9 @@ class TimelineViewTest { } @Test - fun `hide shield dialog`() { + fun `hide shield dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())), isLive = false, @@ -167,16 +166,16 @@ class TimelineViewTest { eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0)) eventsRecorder.clear() - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog) } @Ignore( "performScrollToIndex in compose tests no longer sets LazyListState.isScrollInProgress to true, so the LoadMore event is not emitted." + - "This needs to be reworked to use a different approach to check the LoadMore event was emitted." + "This needs to be reworked to use a different approach to check the LoadMore event was emitted." ) @Test - fun `scrolling near to the start of the loaded items triggers a pre-fetch`() { + fun `scrolling near to the start of the loaded items triggers a pre-fetch`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val items = List(200) { aTimelineItemEvent( @@ -185,7 +184,7 @@ class TimelineViewTest { ) }.toImmutableList() - rule.setTimelineView( + setTimelineView( state = aTimelineState( timelineItems = items, eventSink = eventsRecorder, @@ -194,9 +193,9 @@ class TimelineViewTest { ), ) - rule.onNodeWithTag("timeline").performScrollToIndex(180) + onNodeWithTag("timeline").performScrollToIndex(180) - rule.mainClock.advanceTimeBy(1000) + mainClock.advanceTimeBy(1000) eventsRecorder.assertList( listOf( @@ -207,7 +206,7 @@ class TimelineViewTest { } } -private fun AndroidComposeTestRule.setTimelineView( +private fun AndroidComposeUiTest.setTimelineView( state: TimelineState, timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt index 64b5216d2e..40671e4bf8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.timeline.components.event import androidx.activity.ComponentActivity +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent @@ -20,14 +23,11 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressTag -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class TimelineItemPollViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test fun `answering a poll with first answer should emit a PollAnswerSelected event`() { testAnswer(answerIndex = 0) @@ -38,17 +38,17 @@ class TimelineItemPollViewTest { testAnswer(answerIndex = 1) } - private fun testAnswer(answerIndex: Int) { + private fun testAnswer(answerIndex: Int) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent() - rule.setContent { + setContent { TimelineItemPollView( content = content, eventSink = eventsRecorder ) } val answer = content.answerItems[answerIndex].answer - rule.onNode( + onNode( matcher = hasText(answer.text), useUnmergedTree = true, ).performClick() @@ -56,38 +56,38 @@ class TimelineItemPollViewTest { } @Test - fun `editing a poll should emit a PollEditClicked event`() { + fun `editing a poll should emit a PollEditClicked event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent( isMine = true, isEditable = true, ) - rule.setContent { + setContent { TimelineItemPollView( content = content, eventSink = eventsRecorder ) } - rule.clickOn(CommonStrings.action_edit_poll) + clickOn(CommonStrings.action_edit_poll) eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!)) } @Test - fun `closing a poll should emit a PollEndClicked event`() { + fun `closing a poll should emit a PollEndClicked event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent( isMine = true, ) - rule.setContent { + setContent { TimelineItemPollView( content = content, eventSink = eventsRecorder ) } - rule.clickOn(CommonStrings.action_end_poll) + clickOn(CommonStrings.action_end_poll) // A confirmation dialog should be shown eventsRecorder.assertEmpty() - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt index 154225aa7a..7b8597f05a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.timeline.components.event import android.text.SpannableString import android.text.SpannedString import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -38,45 +41,40 @@ import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.wysiwyg.view.spans.CustomMentionSpan import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class TimelineTextViewTest { - @get:Rule val rule = createAndroidComposeRule() - private val mentionSpanTheme = MentionSpanTheme(currentUserId = A_USER_ID) private val formatLambda = lambdaRecorder { mentionType -> mentionType.toString() } private val mentionSpanFormatter = FakeMentionSpanFormatter(formatLambda) @Test - fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runTest { + fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runAndroidComposeUiTest { val charSequence = "Hello @alice:example.com" val mentionSpanUpdater = aMentionSpanUpdater() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) assertThat(result.getMentionSpans()).isEmpty() assert(formatLambda).isNeverCalled() } @Test - fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runTest { + fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runAndroidComposeUiTest { val charSequence = SpannableString("Hello @alice:example.com") val mentionSpanUpdater = aMentionSpanUpdater() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) assertThat(result.getMentionSpans()).isEmpty() assert(formatLambda).isNeverCalled() } @Test - fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runTest { + fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runAndroidComposeUiTest { val charSequence = "Hello @alice:example.com" val mentionSpanUpdater = aMentionSpanUpdater() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null)) assertThat(result.getMentionSpans()).isEmpty() assertThat(result.toString()).isEqualTo(charSequence) @@ -84,7 +82,7 @@ class TimelineTextViewTest { } @Test - fun `getTextWithResolvedMentions - with Room mention format correctly`() = runTest { + fun `getTextWithResolvedMentions - with Room mention format correctly`() = runAndroidComposeUiTest { val mentionType = MentionType.Room(roomIdOrAlias = A_ROOM_ID_2.toRoomIdOrAlias()) val charSequence = buildSpannedString { append("Hello ") @@ -93,7 +91,7 @@ class TimelineTextViewTest { } } val mentionSpanUpdater = aMentionSpanUpdater() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) val expectedDisplayText = mentionType.toString() assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) @@ -102,7 +100,7 @@ class TimelineTextViewTest { } @Test - fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runTest { + fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runAndroidComposeUiTest { val mentionType = MentionType.User(userId = A_USER_ID) val charSequence = buildSpannedString { append("Hello ") @@ -111,7 +109,7 @@ class TimelineTextViewTest { } } val mentionSpanUpdater = aMentionSpanUpdater() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) val expectedDisplayText = mentionType.toString() assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) @@ -119,7 +117,7 @@ class TimelineTextViewTest { } @Test - fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runTest { + fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runAndroidComposeUiTest { val mentionType = MentionType.User(userId = A_USER_ID) val charSequence = buildSpannedString { append("Hello ") @@ -129,12 +127,12 @@ class TimelineTextViewTest { } val mentionSpanUpdater = aMentionSpanUpdater() val expectedDisplayText = mentionType.toString() - val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) + val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence)) assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText) assert(formatLambda).isCalledOnce() } - private suspend fun AndroidComposeTestRule.getText( + private suspend fun AndroidComposeUiTest.getText( mentionSpanUpdater: MentionSpanUpdater, content: TimelineItemTextBasedContent, ): CharSequence { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt index af3acee6a2..8050278fb2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt @@ -6,56 +6,55 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.messages.impl.timeline.protection import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.lambda.lambdaError -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ProtectedViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `when hideContent is false, the content is rendered`() { - rule.setProtectedView( + fun `when hideContent is false, the content is rendered`() = runAndroidComposeUiTest { + setProtectedView( hideContent = false, content = { Text("Hello") } ) - rule.onNodeWithText("Hello").assertExists() + onNodeWithText("Hello").assertExists() } @Test - fun `when hideContent is true, the content is not rendered, and user can reveal it`() { + fun `when hideContent is true, the content is not rendered, and user can reveal it`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setProtectedView( + setProtectedView( hideContent = true, onShowClick = it, content = { Text("Hello") } ) - rule.onNodeWithText("Hello").assertDoesNotExist() - rule.clickOn(CommonStrings.action_show) + onNodeWithText("Hello").assertDoesNotExist() + clickOn(CommonStrings.action_show) } } } -private fun AndroidComposeTestRule.setProtectedView( +private fun AndroidComposeUiTest.setProtectedView( hideContent: Boolean = false, onShowClick: () -> Unit = { lambdaError() }, content: @Composable () -> Unit = {}, diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt index 1ff25a0a81..a6b97c554c 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.poll.impl.history import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.poll.api.pollcontent.aPollContentState import io.element.android.features.poll.impl.R @@ -26,34 +29,29 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class PollHistoryViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setPollHistoryViewView( + setPollHistoryViewView( aPollHistoryState( eventSink = eventsRecorder ), goBack = it ) - rule.pressBack() + pressBack() } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on edit poll invokes the expected callback`() { + fun `clicking on edit poll invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val eventId = EventId("\$anEventId") val state = aPollHistoryState( @@ -69,17 +67,17 @@ class PollHistoryViewTest { eventSink = eventsRecorder ) ensureCalledOnceWithParam(eventId) { - rule.setPollHistoryViewView( + setPollHistoryViewView( state = state, onEditPoll = it ) - rule.clickOn(CommonStrings.action_edit_poll) + clickOn(CommonStrings.action_edit_poll) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on poll end emits the expected Event`() { + fun `clicking on poll end emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val eventId = EventId("\$anEventId") val state = aPollHistoryState( @@ -95,16 +93,16 @@ class PollHistoryViewTest { ), eventSink = eventsRecorder ) - rule.setPollHistoryViewView( + setPollHistoryViewView( state = state, ) - rule.clickOn(CommonStrings.action_end_poll) + clickOn(CommonStrings.action_end_poll) // Cancel the dialog - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) // Do it again, and confirm the dialog - rule.clickOn(CommonStrings.action_end_poll) + clickOn(CommonStrings.action_end_poll) eventsRecorder.assertEmpty() - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle( PollHistoryEvents.EndPoll(eventId) ) @@ -112,7 +110,7 @@ class PollHistoryViewTest { @Config(qualifiers = "h1024dp") @Test - fun `clicking on poll answer emits the expected Event`() { + fun `clicking on poll answer emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val eventId = EventId("\$anEventId") val state = aPollHistoryState( @@ -129,10 +127,10 @@ class PollHistoryViewTest { eventSink = eventsRecorder ) val answer = state.pollHistoryItems.ongoing.first().state.answerItems.first().answer - rule.setPollHistoryViewView( + setPollHistoryViewView( state = state, ) - rule.onNodeWithText( + onNodeWithText( text = answer.text, useUnmergedTree = true, ).performClick() @@ -142,14 +140,14 @@ class PollHistoryViewTest { } @Test - fun `clicking on past tab emits the expected Event`() { + fun `clicking on past tab emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPollHistoryViewView( + setPollHistoryViewView( aPollHistoryState( eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_polls_history_filter_past) + clickOn(R.string.screen_polls_history_filter_past) eventsRecorder.assertSingle( PollHistoryEvents.SelectFilter(filter = PollHistoryFilter.PAST) ) @@ -157,22 +155,22 @@ class PollHistoryViewTest { @Config(qualifiers = "h1024dp") @Test - fun `clicking on load more emits the expected Event`() { + fun `clicking on load more emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPollHistoryViewView( + setPollHistoryViewView( aPollHistoryState( hasMoreToLoad = true, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_load_more) + clickOn(CommonStrings.action_load_more) eventsRecorder.assertSingle( PollHistoryEvents.LoadMore ) } } -private fun AndroidComposeTestRule.setPollHistoryViewView( +private fun AndroidComposeUiTest.setPollHistoryViewView( state: PollHistoryState, onEditPoll: (EventId) -> Unit = EnsureNeverCalledWithParam(), goBack: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt index 258e9855de..e7ce526843 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.about import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled @@ -19,51 +22,47 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AboutViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes back callback`() { + fun `clicking on back invokes back callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setAboutView( + setAboutView( anAboutState(), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on an item invokes the expected callback`() { + fun `clicking on an item invokes the expected callback`() = runAndroidComposeUiTest { val state = anAboutState() ensureCalledOnceWithParam(state.elementLegals.first()) { callback -> - rule.setAboutView( + setAboutView( state, onElementLegalClick = callback, ) - rule.clickOn(state.elementLegals.first().titleRes) + clickOn(state.elementLegals.first().titleRes) } } @Test - fun `clicking on the open source licenses invokes the expected callback`() { + fun `clicking on the open source licenses invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setAboutView( + setAboutView( anAboutState(), onOpenSourceLicensesClick = callback, ) - rule.clickOn(CommonStrings.common_open_source_licenses) + clickOn(CommonStrings.common_open_source_licenses) } } } -private fun AndroidComposeTestRule.setAboutView( +private fun AndroidComposeUiTest.setAboutView( state: AboutState, onElementLegalClick: (ElementLegal) -> Unit = EnsureNeverCalledWithParam(), onOpenSourceLicensesClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt index e46e350415..b6fe5c3d0b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.advanced import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Interaction @@ -30,104 +33,99 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.toImmutableList -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class AdvancedSettingsViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on other theme emits the expected event`() { + fun `clicking on other theme emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.common_appearance) - rule.clickOn(CommonStrings.common_dark) + clickOn(CommonStrings.common_appearance) + clickOn(CommonStrings.common_dark) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark)) } @Test - fun `black theme is shown when available`() { - rule.setAdvancedSettingsView( + fun `black theme is shown when available`() = runAndroidComposeUiTest { + setAdvancedSettingsView( state = aAdvancedSettingsState( availableThemeOptions = ThemeOption.entries.toImmutableList(), ), ) - rule.clickOn(CommonStrings.common_appearance) - rule.run { - val text = activity.getString(CommonStrings.common_black) + clickOn(CommonStrings.common_appearance) + run { + val text = activity!!.getString(CommonStrings.common_black) onNodeWithText(text).assertExists() } } @Test - fun `black theme is hidden when unavailable`() { - rule.setAdvancedSettingsView( + fun `black theme is hidden when unavailable`() = runAndroidComposeUiTest { + setAdvancedSettingsView( state = aAdvancedSettingsState( availableThemeOptions = ThemeOption.entries.filterNot { it == ThemeOption.Black }.toImmutableList(), ), ) - rule.clickOn(CommonStrings.common_appearance) - rule.assertNoNodeWithText(CommonStrings.common_black) + clickOn(CommonStrings.common_appearance) + assertNoNodeWithText(CommonStrings.common_black) } @Test - fun `clicking on View source emits the expected event`() { + fun `clicking on View source emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_view_source) + clickOn(CommonStrings.action_view_source) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetDeveloperModeEnabled(true)) } @Test - fun `clicking on Share presence emits the expected event`() { + fun `clicking on Share presence emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_advanced_settings_share_presence) + clickOn(R.string.screen_advanced_settings_share_presence) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) } @Test - fun `clicking on media to enable compression emits the expected event`() { + fun `clicking on media to enable compression emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val analyticsService = FakeAnalyticsService() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), analyticsService = analyticsService ) - rule.clickOn(R.string.screen_advanced_settings_media_compression_description) + clickOn(R.string.screen_advanced_settings_media_compression_description) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(true)) assertThat(analyticsService.capturedEvents).isEqualTo( listOf( @@ -139,17 +137,17 @@ class AdvancedSettingsViewTest { } @Test - fun `clicking on media to disable compression emits the expected event`() { + fun `clicking on media to disable compression emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val analyticsService = FakeAnalyticsService() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( mediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = true), eventSink = eventsRecorder, ), analyticsService = analyticsService ) - rule.clickOn(R.string.screen_advanced_settings_media_compression_description) + clickOn(R.string.screen_advanced_settings_media_compression_description) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(false)) assertThat(analyticsService.capturedEvents).isEqualTo( listOf( @@ -162,65 +160,65 @@ class AdvancedSettingsViewTest { @Test @Config(qualifiers = "h1080dp") - fun `clicking on hide invite avatars emits the expected event`() { + fun `clicking on hide invite avatars emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, hideInviteAvatars = false ), ) - rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title) + clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetHideInviteAvatars(true)) } @Test @Config(qualifiers = "h1080dp") - fun `clicking on timeline media preview always hide emits the expected event`() { + fun `clicking on timeline media preview always hide emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, timelineMediaPreviewValue = MediaPreviewValue.On ), ) - rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) + clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) } @Test @Config(qualifiers = "h1080dp") - fun `clicking on timeline media preview private rooms emits the expected event`() { + fun `clicking on timeline media preview private rooms emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, timelineMediaPreviewValue = MediaPreviewValue.On ), ) - rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) + clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) } @Test @Config(qualifiers = "h1080dp") - fun `clicking on timeline media preview always show emits the expected event`() { + fun `clicking on timeline media preview always show emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, timelineMediaPreviewValue = MediaPreviewValue.Off ), ) - rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show) + clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) } @Test @Config(qualifiers = "h1080dp") - fun `hide invite avatars toggle is disabled when action is loading`() { + fun `hide invite avatars toggle is disabled when action is loading`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, hideInviteAvatars = false, @@ -228,14 +226,14 @@ class AdvancedSettingsViewTest { ), ) // The toggle should be disabled, so clicking should not emit any events - rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title) + clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title) } @Test @Config(qualifiers = "h1080dp") - fun `timeline media preview options are disabled when action is loading`() { + fun `timeline media preview options are disabled when action is loading`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setAdvancedSettingsView( + setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, timelineMediaPreviewValue = MediaPreviewValue.On, @@ -243,12 +241,12 @@ class AdvancedSettingsViewTest { ), ) // The options should be disabled, so clicking should not emit any events - rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) - rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) + clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) + clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) } } -private fun AndroidComposeTestRule.setAdvancedSettingsView( +private fun AndroidComposeUiTest.setAdvancedSettingsView( state: AdvancedSettingsState, analyticsService: AnalyticsService = FakeAnalyticsService(), onBackClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt index b3549762ab..993d14caab 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.blockedusers import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -23,72 +26,67 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BlockedUserViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes back callback`() { + fun `clicking on back invokes back callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setBlockedUsersView( + setBlockedUsersView( aBlockedUsersState( eventSink = eventsRecorder ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on a user emits the expected Event`() { + fun `clicking on a user emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val userList = aMatrixUserList() - rule.setBlockedUsersView( + setBlockedUsersView( aBlockedUsersState( blockedUsers = userList, eventSink = eventsRecorder ), ) - rule.onNodeWithText(userList.first().displayName.orEmpty()).performClick() + onNodeWithText(userList.first().displayName.orEmpty()).performClick() eventsRecorder.assertSingle(BlockedUsersEvents.Unblock(userList.first().userId)) } @Test - fun `clicking on cancel sends a BlockedUsersEvents`() { + fun `clicking on cancel sends a BlockedUsersEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setBlockedUsersView( + setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(BlockedUsersEvents.Cancel) } @Test - fun `clicking on confirm sends a BlockedUsersEvents`() { + fun `clicking on confirm sends a BlockedUsersEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setBlockedUsersView( + setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_blocked_users_unblock_alert_action) + clickOn(R.string.screen_blocked_users_unblock_alert_action) eventsRecorder.assertSingle(BlockedUsersEvents.ConfirmUnblock) } } -private fun AndroidComposeTestRule.setBlockedUsersView( +private fun AndroidComposeUiTest.setBlockedUsersView( state: BlockedUsersState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index d4d02d7de9..61d7278a8a 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.developer import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R import io.element.android.tests.testutils.EnsureNeverCalled @@ -20,76 +23,71 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class DeveloperSettingsViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setDeveloperSettingsView( + setDeveloperSettingsView( state = aDeveloperSettingsState( eventSink = eventsRecorder ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Config(qualifiers = "h2000dp") @Test - fun `clicking on push history notification invokes the expected callback`() { + fun `clicking on push history notification invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setDeveloperSettingsView( + setDeveloperSettingsView( state = aDeveloperSettingsState( eventSink = eventsRecorder ), onPushHistoryClick = it ) - rule.clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title) + clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title) } } @Config(qualifiers = "h2000dp") @Test - fun `clicking on open showkase invokes the expected callback`() { + fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setDeveloperSettingsView( + setDeveloperSettingsView( state = aDeveloperSettingsState( eventSink = eventsRecorder ), onOpenShowkase = it ) - rule.onNodeWithText("Open Showkase browser").performClick() + onNodeWithText("Open Showkase browser").performClick() } } @Config(qualifiers = "h2200dp") @Test - fun `clicking on clear cache emits the expected event`() { + fun `clicking on clear cache emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setDeveloperSettingsView( + setDeveloperSettingsView( state = aDeveloperSettingsState( eventSink = eventsRecorder ), ) - rule.onNodeWithText("Clear cache").performClick() + onNodeWithText("Clear cache").performClick() eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache) } } -private fun AndroidComposeTestRule.setDeveloperSettingsView( +private fun AndroidComposeUiTest.setDeveloperSettingsView( state: DeveloperSettingsState, onOpenShowkase: () -> Unit = EnsureNeverCalled(), onPushHistoryClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/appsettings/AppDeveloperSettingsPageTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/appsettings/AppDeveloperSettingsPageTest.kt index 123f31ae8e..17218c6ab5 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/appsettings/AppDeveloperSettingsPageTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/appsettings/AppDeveloperSettingsPageTest.kt @@ -5,19 +5,22 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.developer.appsettings import androidx.activity.ComponentActivity +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.isEditable import androidx.compose.ui.test.isFocusable -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem @@ -27,78 +30,73 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class AppDeveloperSettingsPageTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setAppDeveloperSettingsView( + setAppDeveloperSettingsView( state = anAppDeveloperSettingsState( eventSink = eventsRecorder ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Config(qualifiers = "h1500dp") @Test - fun `clicking on element call url open the dialogs and submit emits the expected event`() { + fun `clicking on element call url open the dialogs and submit emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAppDeveloperSettingsView( + setAppDeveloperSettingsView( state = anAppDeveloperSettingsState( eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_advanced_settings_element_call_base_url) - val textInputNode = rule.onAllNodes(isEditable().and(isFocusable())).filterToOne(hasAnyAncestor(isDialog())) + clickOn(R.string.screen_advanced_settings_element_call_base_url) + val textInputNode = onAllNodes(isEditable().and(isFocusable())).filterToOne(hasAnyAncestor(isDialog())) textInputNode.performTextInput("https://call.element.dev") - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetCustomElementCallBaseUrl("https://call.element.dev")) } @Config(qualifiers = "h2000dp") @Test - fun `clicking on open showkase invokes the expected callback`() { + fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setAppDeveloperSettingsView( + setAppDeveloperSettingsView( state = anAppDeveloperSettingsState( eventSink = eventsRecorder ), onOpenShowkase = it ) - rule.onNodeWithText("Open Showkase browser").performClick() + onNodeWithText("Open Showkase browser").performClick() } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on log level emits the expected event`() { + fun `clicking on log level emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAppDeveloperSettingsView( + setAppDeveloperSettingsView( state = anAppDeveloperSettingsState( eventSink = eventsRecorder ), ) - rule.onNodeWithText("Tracing log level").performClick() - rule.onNodeWithText("Debug").performClick() + onNodeWithText("Tracing log level").performClick() + onNodeWithText("Debug").performClick() eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetTracingLogLevel(LogLevelItem.DEBUG)) } } -private fun AndroidComposeTestRule.setAppDeveloperSettingsView( +private fun AndroidComposeUiTest.setAppDeveloperSettingsView( state: AppDeveloperSettingsState, onOpenShowkase: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt index ea140abbd7..66ed0339a3 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.notifications import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -25,76 +28,71 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class NotificationSettingsViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onBackClick = it ) - rule.pressBack() + pressBack() } eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on troubleshoot notification invokes the expected callback`() { + fun `clicking on troubleshoot notification invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onTroubleshootNotificationsClick = it ) - rule.clickOn(R.string.troubleshoot_notifications_entry_point_title) + clickOn(R.string.troubleshoot_notifications_entry_point_title) } eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on group chats invokes the expected callback`() { + fun `clicking on group chats invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(false) { - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onOpenEditDefault = it ) - rule.clickOn(R.string.screen_notification_settings_group_chats) + clickOn(R.string.screen_notification_settings_group_chats) } eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on direct chats invokes the expected callback`() { + fun `clicking on direct chats invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(true) { - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onOpenEditDefault = it ) - rule.clickOn(R.string.screen_notification_settings_direct_chats) + clickOn(R.string.screen_notification_settings_direct_chats) } eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) } @@ -111,15 +109,15 @@ class NotificationSettingsViewTest { testNotificationToggle(false) } - private fun testNotificationToggle(initialState: Boolean) { + private fun testNotificationToggle(initialState: Boolean) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( appNotificationEnabled = initialState, eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_notification_settings_enable_notifications) + clickOn(R.string.screen_notification_settings_enable_notifications) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -140,15 +138,15 @@ class NotificationSettingsViewTest { testAtRoomToggle(false) } - private fun testAtRoomToggle(initialState: Boolean) { + private fun testAtRoomToggle(initialState: Boolean) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( atRoomNotificationsEnabled = initialState, eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_notification_settings_room_mention_label) + clickOn(R.string.screen_notification_settings_room_mention_label) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -169,15 +167,15 @@ class NotificationSettingsViewTest { testInvitationToggle(false) } - private fun testInvitationToggle(initialState: Boolean) { + private fun testInvitationToggle(initialState: Boolean) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( inviteForMeNotificationsEnabled = initialState, eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_notification_settings_invite_for_me_label) + clickOn(R.string.screen_notification_settings_invite_for_me_label) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -188,15 +186,15 @@ class NotificationSettingsViewTest { @Config(qualifiers = "h1024dp") @Test - fun `with an error configuration, clicking on continue emits the expected events`() { + fun `with an error configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -207,15 +205,15 @@ class NotificationSettingsViewTest { @Config(qualifiers = "h1024dp") @Test - fun `with invalid configuration, clicking on continue emits the expected events`() { + fun `with invalid configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aInvalidNotificationSettingsState( fixFailed = false, eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -226,15 +224,15 @@ class NotificationSettingsViewTest { @Config(qualifiers = "h1024dp") @Test - fun `with invalid configuration and error, clicking on OK emits the expected events`() { + fun `with invalid configuration and error, clicking on OK emits the expected events`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aInvalidNotificationSettingsState( fixFailed = true, eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -245,14 +243,14 @@ class NotificationSettingsViewTest { @Config(qualifiers = "h1024dp") @Test - fun `clicking on Push notification provider emits the expected event`() { + fun `clicking on Push notification provider emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_advanced_settings_push_provider_android) + clickOn(R.string.screen_advanced_settings_push_provider_android) eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -262,16 +260,16 @@ class NotificationSettingsViewTest { } @Test - fun `clicking on a push provider emits the expected event`() { + fun `clicking on a push provider emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setNotificationSettingsView( + setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder, showChangePushProviderDialog = true, availablePushDistributors = listOf(aDistributor("P1"), aDistributor("P2")) ), ) - rule.onNodeWithText("P2").performClick() + onNodeWithText("P2").performClick() eventsRecorder.assertList( listOf( NotificationSettingsEvents.RefreshSystemNotificationsEnabled, @@ -281,7 +279,7 @@ class NotificationSettingsViewTest { } } -private fun AndroidComposeTestRule.setNotificationSettingsView( +private fun AndroidComposeUiTest.setNotificationSettingsView( state: NotificationSettingsState, onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(), onTroubleshootNotificationsClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt index da91bdbf86..88ebbf64a1 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt @@ -5,13 +5,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R import io.element.android.libraries.matrix.api.user.MatrixUser @@ -25,49 +28,45 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PreferencesRootViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes back callback`() { + fun `clicking on back invokes back callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder ), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `click on User profile invokes the expected callback`() { + fun `click on User profile invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val user = aMatrixUser() ensureCalledOnceWithParam(user) { callback -> - rule.setView( + setView( aPreferencesRootState( myUser = user, eventSink = eventsRecorder, ), onOpenUserProfile = callback, ) - rule.onNodeWithText("Alice").performClick() + onNodeWithText("Alice").performClick() } } @Test - fun `clicking on other session sends a SwitchToSession`() { + fun `clicking on other session sends a SwitchToSession`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setView( + setView( aPreferencesRootState( isMultiAccountEnabled = true, otherSessions = listOf( @@ -79,366 +78,366 @@ class PreferencesRootViewTest { eventSink = eventsRecorder, ), ) - rule.onNodeWithText("Bob").performClick() + onNodeWithText("Bob").performClick() eventsRecorder.assertSingle(PreferencesRootEvent.SwitchToSession(A_USER_ID_2)) } @Test - fun `click on Add account invokes the expected callback`() { + fun `click on Add account invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( isMultiAccountEnabled = true, eventSink = eventsRecorder, ), onAddAccountClick = callback, ) - rule.clickOn(CommonStrings.common_add_another_account) + clickOn(CommonStrings.common_add_another_account) } } @Test - fun `when multi account is not enabled, item is not shown`() { + fun `when multi account is not enabled, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( isMultiAccountEnabled = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_add_another_account)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_add_another_account)).assertDoesNotExist() } @Test - fun `click on Encryption invokes the expected callback`() { + fun `click on Encryption invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( showSecureBackup = true, eventSink = eventsRecorder, ), onSecureBackupClick = callback, ) - rule.clickOn(CommonStrings.common_encryption) + clickOn(CommonStrings.common_encryption) } } @Test - fun `when showSecureBackup is false, item is not shown`() { + fun `when showSecureBackup is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( showSecureBackup = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_encryption)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_encryption)).assertDoesNotExist() } @Test - fun `click on Manage account invokes the expected callback`() { + fun `click on Manage account invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam("aUrl") { callback -> - rule.setView( + setView( aPreferencesRootState( accountManagementUrl = "aUrl", eventSink = eventsRecorder, ), onManageAccountClick = callback, ) - rule.clickOn(CommonStrings.action_manage_account_and_devices) + clickOn(CommonStrings.action_manage_account_and_devices) } } @Test - fun `when accountManagementUrl is null, item is not shown`() { + fun `when accountManagementUrl is null, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( accountManagementUrl = null, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_manage_account_and_devices)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.action_manage_account_and_devices)).assertDoesNotExist() } @Test - fun `click on Link new devices invokes the expected callback`() { + fun `click on Link new devices invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( showLinkNewDevice = true, eventSink = eventsRecorder, ), onLinkNewDeviceClick = callback, ) - rule.clickOn(CommonStrings.common_link_new_device) + clickOn(CommonStrings.common_link_new_device) } } @Test - fun `when showLinkNewDevice is false, item is not shown`() { + fun `when showLinkNewDevice is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( showLinkNewDevice = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_link_new_device)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_link_new_device)).assertDoesNotExist() } @Test - fun `click on Analytics invokes the expected callback`() { + fun `click on Analytics invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( showAnalyticsSettings = true, eventSink = eventsRecorder, ), onOpenAnalytics = callback, ) - rule.clickOn(CommonStrings.common_analytics) + clickOn(CommonStrings.common_analytics) } } @Test - fun `when showAnalyticsSettings is false, item is not shown`() { + fun `when showAnalyticsSettings is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( showAnalyticsSettings = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_analytics)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_analytics)).assertDoesNotExist() } @Test - fun `click on Report a problem invokes the expected callback`() { + fun `click on Report a problem invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( canReportBug = true, eventSink = eventsRecorder, ), onOpenRageShake = callback, ) - rule.clickOn(CommonStrings.common_report_a_problem) + clickOn(CommonStrings.common_report_a_problem) } } @Test - fun `when canReportBug is false, item is not shown`() { + fun `when canReportBug is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( canReportBug = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_report_a_problem)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_report_a_problem)).assertDoesNotExist() } @Test - fun `click on Screen lock invokes the expected callback`() { + fun `click on Screen lock invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder, ), onOpenLockScreenSettings = callback, ) - rule.clickOn(CommonStrings.common_screen_lock) + clickOn(CommonStrings.common_screen_lock) } } @Test - fun `click on About invokes the expected callback`() { + fun `click on About invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder, ), onOpenAbout = callback, ) - rule.clickOn(CommonStrings.common_about) + clickOn(CommonStrings.common_about) } } @Test - fun `click on Developer settings invokes the expected callback`() { + fun `click on Developer settings invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( showDeveloperSettings = true, eventSink = eventsRecorder, ), onOpenDeveloperSettings = callback, ) - rule.clickOn(CommonStrings.common_developer_options) + clickOn(CommonStrings.common_developer_options) } } @Test - fun `when showDeveloperSettings is false, item is not shown`() { + fun `when showDeveloperSettings is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( showDeveloperSettings = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_developer_options)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_developer_options)).assertDoesNotExist() } @Test - fun `click on Advanced settings invokes the expected callback`() { + fun `click on Advanced settings invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder, ), onOpenAdvancedSettings = callback, ) - rule.clickOn(CommonStrings.common_advanced_settings) + clickOn(CommonStrings.common_advanced_settings) } } @Test - fun `click on Labs invokes the expected callback`() { + fun `click on Labs invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( showLabsItem = true, eventSink = eventsRecorder, ), onOpenLabs = callback, ) - rule.clickOn(R.string.screen_labs_title) + clickOn(R.string.screen_labs_title) } } @Test - fun `when showLabsItem is false, item is not shown`() { + fun `when showLabsItem is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( showLabsItem = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(R.string.screen_labs_title)).assertDoesNotExist() + onNodeWithText(activity!!.getString(R.string.screen_labs_title)).assertDoesNotExist() } @Test - fun `click on Notification invokes the expected callback`() { + fun `click on Notification invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder, ), onOpenNotificationSettings = callback, ) - rule.clickOn(R.string.screen_notification_settings_title) + clickOn(R.string.screen_notification_settings_title) } } @Test - fun `click on Blocked users invokes the expected callback`() { + fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( nbOfBlockedUsers = 1, eventSink = eventsRecorder, ), onOpenBlockedUsers = callback, ) - rule.clickOn(CommonStrings.common_blocked_users) + clickOn(CommonStrings.common_blocked_users) } } @Test - fun `when nbOfBlockedUsers is 0, item is not shown`() { + fun `when nbOfBlockedUsers is 0, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( nbOfBlockedUsers = 0, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.common_blocked_users)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist() } @Test - fun `click on Remove this device invokes the expected callback`() { + fun `click on Remove this device invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( eventSink = eventsRecorder, ), onSignOutClick = callback, ) - rule.clickOn(CommonStrings.action_signout) + clickOn(CommonStrings.action_signout) } } @Test - fun `click on Deactivate invokes the expected callback`() { + fun `click on Deactivate invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setView( + setView( aPreferencesRootState( canDeactivateAccount = true, eventSink = eventsRecorder, ), onDeactivateClick = callback, ) - rule.clickOn(CommonStrings.action_delete_account) + clickOn(CommonStrings.action_delete_account) } } @Test - fun `when canDeactivateAccount is false, item is not shown`() { + fun `when canDeactivateAccount is false, item is not shown`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setView( + setView( aPreferencesRootState( canDeactivateAccount = false, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_delete_account)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.action_delete_account)).assertDoesNotExist() } @Test - fun `clicking on version sends a PreferencesRootEvents`() { + fun `clicking on version sends a PreferencesRootEvents`() = runAndroidComposeUiTest { val version = "VERSION" val eventsRecorder = EventsRecorder() - rule.setView( + setView( aPreferencesRootState( version = version, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(version).performClick() + onNodeWithText(version).performClick() eventsRecorder.assertSingle(PreferencesRootEvent.OnVersionInfoClick) } } -private fun AndroidComposeTestRule.setView( +private fun AndroidComposeUiTest.setView( state: PreferencesRootState, onBackClick: () -> Unit = EnsureNeverCalled(), onAddAccountClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt index 728e05ee7e..20db955955 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.preferences.impl.user.editprofile import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -23,96 +26,93 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EditUserProfileViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back emits the expected event`() { + fun `clicking on back emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( eventSink = eventsRecorder, ), ) - rule.pressBack() + pressBack() eventsRecorder.assertSingle(EditUserProfileEvent.Exit) } @Test - fun `clicking on save from the exit confirmation dialog emits the expected event`() { + fun `clicking on save from the exit confirmation dialog emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save, inDialog = true) + clickOn(CommonStrings.action_save, inDialog = true) eventsRecorder.assertSingle(EditUserProfileEvent.Save) } @Test - fun `clicking on discard exit emits the expected event`() { + fun `clicking on discard exit emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_discard) + clickOn(CommonStrings.action_discard) eventsRecorder.assertSingle(EditUserProfileEvent.Exit) } @Test - fun `clicking on save emits the expected event`() { + fun `clicking on save emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( saveButtonEnabled = true, saveAction = AsyncAction.Uninitialized, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertSingle(EditUserProfileEvent.Save) } @Test - fun `clicking on avatar opens the bottom sheet dialog`() { + fun `clicking on avatar opens the bottom sheet dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val actions = listOf( AvatarAction.TakePhoto, AvatarAction.ChoosePhoto, AvatarAction.Remove, ) - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.Uninitialized, avatarActions = actions, eventSink = eventsRecorder, ), ) - val contentDescription = rule.activity.getString(CommonStrings.a11y_avatar) - rule.onNodeWithContentDescription(contentDescription).performClick() + val resources = activity!!.resources + val contentDescription = resources.getString(CommonStrings.a11y_avatar) + onNodeWithContentDescription(contentDescription).performClick() // Assert that the actions are displayed actions.forEach { action -> - val text = rule.activity.getString(action.titleResId) - rule.onNodeWithText(text).assertExists() + val text = resources.getString(action.titleResId) + onNodeWithText(text).assertExists() } } @Test - fun `success invokes the expected callback`() { + fun `success invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setEditUserProfileView( + setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.Success(Unit), eventSink = eventsRecorder, @@ -123,7 +123,7 @@ class EditUserProfileViewTest { } } -private fun AndroidComposeTestRule.setEditUserProfileView( +private fun AndroidComposeUiTest.setEditUserProfileView( state: EditUserProfileState, onEditProfileSuccess: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt index 59d9507571..a18c82b275 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.reportroom.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled @@ -20,76 +23,72 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ReportRoomViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invoke the expected callback`() { + fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setReportRoomView( + setReportRoomView( aReportRoomState( eventSink = eventsRecorder, ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on report when enabled emits the expected event`() { + fun `clicking on report when enabled emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setReportRoomView( + setReportRoomView( aReportRoomState( reason = "Spam", eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_report) + clickOn(CommonStrings.action_report) eventsRecorder.assertSingle(ReportRoomEvents.Report) } @Test - fun `clicking on decline when disabled does not emit event`() { + fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setReportRoomView( + setReportRoomView( aReportRoomState(eventSink = eventsRecorder), ) - rule.clickOn(CommonStrings.action_report) + clickOn(CommonStrings.action_report) } @Test - fun `clicking on leave room option emits the expected event`() { + fun `clicking on leave room option emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setReportRoomView( + setReportRoomView( aReportRoomState(eventSink = eventsRecorder), ) - rule.clickOn(CommonStrings.action_leave_room) + clickOn(CommonStrings.action_leave_room) eventsRecorder.assertSingle(ReportRoomEvents.ToggleLeaveRoom) } @Test - fun `typing text in the reason field emits the expected Event`() { + fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setReportRoomView( + setReportRoomView( aReportRoomState( eventSink = eventsRecorder, reason = "" ), ) - rule.onNodeWithText("").performTextInput("Spam!") + onNodeWithText("").performTextInput("Spam!") eventsRecorder.assertSingle(ReportRoomEvents.UpdateReason("Spam!")) } } -private fun AndroidComposeTestRule.setReportRoomView( +private fun AndroidComposeUiTest.setReportRoomView( state: ReportRoomState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt index f28c9c150f..668c6bb221 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.rolesandpermissions.impl.permissions import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -23,84 +26,80 @@ import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ChangeRoomPermissionsViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `click on back icon invokes Exit`() { + fun `click on back icon invokes Exit`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( eventSink = recorder ) ) - rule.pressBack() + pressBack() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) } @Test - fun `click on back key invokes Exit`() { + fun `click on back key invokes Exit`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( eventSink = recorder ) ) - rule.pressBackKey() + pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) } @Test - fun `when confirming exit with pending changes, using the back key actually exits`() { + fun `when confirming exit with pending changes, using the back key actually exits`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, eventSink = recorder, ), ) - rule.pressBackKey() + pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) } @Test - fun `when confirming exit with pending changes, clicking on 'discard' button in the dialog actually exits`() { + fun `when confirming exit with pending changes, clicking on 'discard' button in the dialog actually exits`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_discard) + clickOn(CommonStrings.action_discard) recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) } @Test - fun `when confirming exit with pending changes, clicking on 'save' button in the dialog saves the changes`() { + fun `when confirming exit with pending changes, clicking on 'save' button in the dialog saves the changes`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_save, inDialog = true) + clickOn(CommonStrings.action_save, inDialog = true) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) } @Test - fun `click on a role item triggers ChangeRole event`() { + fun `click on a role item triggers ChangeRole event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( itemsBySection = persistentMapOf( // Makes sure there is only one item to click on @@ -109,70 +108,70 @@ class ChangeRoomPermissionsViewTest { eventSink = recorder, ) ) - rule.clickOn(R.string.screen_room_change_permissions_room_name) - rule.clickOn(R.string.screen_room_change_permissions_everyone) + clickOn(R.string.screen_room_change_permissions_room_name) + clickOn(R.string.screen_room_change_permissions_everyone) recorder.assertSingle( ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Everyone), ) } @Test - fun `click on the Save menu item triggers Save event`() { + fun `click on the Save menu item triggers Save event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) } @Test - fun `a successful save exits the screen`() { + fun `a successful save exits the screen`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(true) { callback -> - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, saveAction = AsyncAction.Success(true), ), onComplete = callback, ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) } } @Test - fun `a cancellation exits the screen`() { + fun `a cancellation exits the screen`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(false) { callback -> - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, saveAction = AsyncAction.Success(false), ), onComplete = callback, ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) } } @Test - fun `click on the Ok option in save error dialog triggers ResetPendingAction event`() { + fun `click on the Ok option in save error dialog triggers ResetPendingAction event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setChangeRoomPermissionsRule( + setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")), eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions) } } -private fun AndroidComposeTestRule.setChangeRoomPermissionsRule( +private fun AndroidComposeUiTest.setChangeRoomPermissionsRule( state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(), onComplete: (Boolean) -> Unit = EnsureNeverCalledWithParam(), ) { diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt index 09bef49cbd..62d0608f26 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt @@ -6,15 +6,18 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.rolesandpermissions.impl.roles import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction @@ -30,20 +33,16 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey import kotlinx.collections.immutable.toImmutableList -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class ChangeRolesViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `passing a 'User' role throws an exception`() { + fun `passing a 'User' role throws an exception`() = runAndroidComposeUiTest { val exception = runCatchingExceptions { - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.User, eventSink = EnsureNeverCalledWithParam(), @@ -54,106 +53,106 @@ class ChangeRolesViewTest { } @Test - fun `back key - with search active toggles the search`() { + fun `back key - with search active toggles the search`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, eventSink = eventsRecorder, ), ) - rule.pressBackKey() + pressBackKey() // Advance time to let the event be processed, as the search toggle might have some delay (e.g. for the animation) - rule.mainClock.advanceTimeBy(1) + mainClock.advanceTimeBy(1) eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive) } @Test - fun `back key - with search inactive exits the screen`() { + fun `back key - with search inactive exits the screen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = false, eventSink = eventsRecorder, ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `back button - exits the screen`() { + fun `back button - exits the screen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = false, eventSink = eventsRecorder, ), ) - rule.pressBack() + pressBack() eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `save button - with changes, it saves them`() { + fun `save button - with changes, it saves them`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( hasPendingChanges = true, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save button - with no changes, does nothing`() { + fun `save button - with no changes, does nothing`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( hasPendingChanges = false, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertEmpty() } @Test - fun `exit confirmation dialog - discard exits the screen`() { + fun `exit confirmation dialog - discard exits the screen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, savingState = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_discard) + clickOn(CommonStrings.action_discard) eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `exit confirmation dialog - save emits the save event`() { + fun `exit confirmation dialog - save emits the save event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, savingState = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save admins confirmation dialog - submit saves the changes`() { + fun `save admins confirmation dialog - submit saves the changes`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Admin, isSearchActive = true, @@ -161,14 +160,14 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save owners confirmation dialog - continue saves the changes`() { + fun `save owners confirmation dialog - continue saves the changes`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Owner(isCreator = false), isSearchActive = true, @@ -176,14 +175,14 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save admins confirmation dialog - cancel removes the dialog`() { + fun `save admins confirmation dialog - cancel removes the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Admin, isSearchActive = true, @@ -191,14 +190,14 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) } @Test - fun `save owners confirmation dialog - cancel removes the dialog`() { + fun `save owners confirmation dialog - cancel removes the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Owner(isCreator = false), isSearchActive = true, @@ -206,39 +205,39 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) } @Test - fun `error dialog - dismissing removes the dialog`() { + fun `error dialog - dismissing removes the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, savingState = AsyncAction.Failure(IllegalStateException("boom")), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) } @Test - fun `testing removing user from selected list emits the expected event`() { + fun `testing removing user from selected list emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val selectedUsers = aMatrixUserList().take(2) val userToDeselect = selectedUsers[1] assertThat(userToDeselect.displayName).isEqualTo("Bob") - rule.setChangeRolesContent( + setChangeRolesContent( state = aChangeRolesStateWithSelectedUsers().copy( selectedUsers = selectedUsers.toImmutableList(), eventSink = eventsRecorder, ), ) // Unselect the user from the row list - val contentDescription = rule.activity.getString(CommonStrings.action_remove) - rule.onNodeWithContentDescription( + val contentDescription = activity!!.getString(CommonStrings.action_remove) + onNodeWithContentDescription( label = contentDescription, useUnmergedTree = true, ).performClick() @@ -247,7 +246,7 @@ class ChangeRolesViewTest { @Test @Config(qualifiers = "h1000dp") - fun `testing adding user to the selected list emits the expected event`() { + fun `testing adding user to the selected list emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val selectedUsers = aMatrixUserList().take(2) val state = aChangeRolesStateWithSelectedUsers().copy( @@ -256,16 +255,16 @@ class ChangeRolesViewTest { ) val userToSelect = (state.searchResults as SearchBarResultState.Results).results.members.first().toMatrixUser() assertThat(userToSelect.displayName).isEqualTo("Carol") - rule.setChangeRolesContent( + setChangeRolesContent( state = state, ) // Select the user from the user list - rule.onNodeWithText("Carol").performClick() + onNodeWithText("Carol").performClick() eventsRecorder.assertSingle(ChangeRolesEvent.UserSelectionToggled(userToSelect)) } @Test - fun `testing removing user to the selected list emits the expected event`() { + fun `testing removing user to the selected list emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val selectedUsers = aMatrixUserList().take(2) val state = aChangeRolesStateWithSelectedUsers().copy( @@ -274,18 +273,18 @@ class ChangeRolesViewTest { ) val userToSelect = (state.searchResults as SearchBarResultState.Results).results.moderators.first().toMatrixUser() assertThat(userToSelect.displayName).isEqualTo("Bob") - rule.setChangeRolesContent( + setChangeRolesContent( state = state, ) // Unselect the user from the user list - rule.onAllNodesWithText( + onAllNodesWithText( text = "Bob", useUnmergedTree = true, )[1].performClick() eventsRecorder.assertSingle(ChangeRolesEvent.UserSelectionToggled(userToSelect)) } - private fun AndroidComposeTestRule.setChangeRolesContent( + private fun AndroidComposeUiTest.setChangeRolesContent( state: ChangeRolesState, ) { setContent { diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt index e08ae205b7..d8908c405d 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.rolesandpermissions.impl.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -23,159 +26,154 @@ import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledTimes import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.setSafeContent -import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class RolesAndPermissionsViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `click on back invokes expected callback`() { + fun `click on back invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( goBack = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `tapping on Admins opens admin list`() { + fun `tapping on Admins opens admin list`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( aRolesAndPermissionsState( roomSupportsOwners = false, eventSink = EventsRecorder(expectEvents = false) ), openAdminList = callback, ) - rule.clickOn(R.string.screen_room_roles_and_permissions_admins) + clickOn(R.string.screen_room_roles_and_permissions_admins) } } @Test - fun `tapping on Admins and Owners opens admin list`() { + fun `tapping on Admins and Owners opens admin list`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( aRolesAndPermissionsState( roomSupportsOwners = true, eventSink = EventsRecorder(expectEvents = false) ), openAdminList = callback, ) - rule.clickOn(R.string.screen_room_roles_and_permissions_admins_and_owners) + clickOn(R.string.screen_room_roles_and_permissions_admins_and_owners) } } @Test - fun `tapping on Moderators opens moderators list`() { + fun `tapping on Moderators opens moderators list`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( openModeratorList = callback, ) - rule.clickOn(R.string.screen_room_roles_and_permissions_moderators) + clickOn(R.string.screen_room_roles_and_permissions_moderators) } } @Test @Config(qualifiers = "h640dp") - fun `tapping permission item open the change permissions screen`() { + fun `tapping permission item open the change permissions screen`() = runAndroidComposeUiTest { ensureCalledTimes(1) { callback -> - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( openEditPermissions = callback, ) - rule.clickOn(R.string.screen_room_roles_and_permissions_permissions_header) + clickOn(R.string.screen_room_roles_and_permissions_permissions_header) } } @Test @Config(qualifiers = "h640dp") - fun `tapping on reset permissions triggers ResetPermissions event`() { + fun `tapping on reset permissions triggers ResetPermissions event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( eventSink = recorder, ), ) - rule.clickOn(R.string.screen_room_roles_and_permissions_reset) + clickOn(R.string.screen_room_roles_and_permissions_reset) recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) } @Test - fun `tapping on Reset in the reset permissions confirmation dialog triggers ResetPermissions event`() { + fun `tapping on Reset in the reset permissions confirmation dialog triggers ResetPermissions event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( resetPermissionsAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_reset) + clickOn(CommonStrings.action_reset) recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) } @Test - fun `tapping on Cancel in the reset permissions confirmation dialog triggers CancelPendingAction event`() { + fun `tapping on Cancel in the reset permissions confirmation dialog triggers CancelPendingAction event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( resetPermissionsAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) } @Test - fun `tapping on 'Demote to moderator' in the demote self bottom sheet triggers the right event`() { + fun `tapping on 'Demote to moderator' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), ) - rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator) - rule.mainClock.advanceTimeBy(1_000L) + clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator) + mainClock.advanceTimeBy(1_000L) recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) } @Test - fun `tapping on 'Demote to member' in the demote self bottom sheet triggers the right event`() = runTest { + fun `tapping on 'Demote to member' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), ) - rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member) - rule.mainClock.advanceTimeBy(1_000L) + clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member) + mainClock.advanceTimeBy(1_000L) recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) } @Test - fun `tapping on 'Cancel' in the demote self bottom sheet triggers the right event`() { + fun `tapping on 'Cancel' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setRolesAndPermissionsView( + setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), ) - rule.clickOn(CommonStrings.action_cancel) - rule.mainClock.advanceTimeBy(1_000L) + clickOn(CommonStrings.action_cancel) + mainClock.advanceTimeBy(1_000L) recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) } } -private fun AndroidComposeTestRule.setRolesAndPermissionsView( +private fun AndroidComposeUiTest.setRolesAndPermissionsView( state: RolesAndPermissionsState = aRolesAndPermissionsState( roomSupportsOwners = false, eventSink = EventsRecorder(expectEvents = false), diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt index 4b37f993f9..5f871183d6 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.roomaliasresolver.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias @@ -22,48 +25,44 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomAliasHelperViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setRoomAliasResolverView( + setRoomAliasResolverView( aRoomAliasResolverState( eventSink = eventsRecorder, ), onBackClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on Retry emits the expected Event`() { + fun `clicking on Retry emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomAliasResolverView( + setRoomAliasResolverView( aRoomAliasResolverState( resolveState = AsyncData.Failure(Exception("Error")), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_retry) + clickOn(CommonStrings.action_retry) eventsRecorder.assertSingle(RoomAliasResolverEvents.Retry) } @Test - fun `success state invokes the expected Callback`() { + fun `success state invokes the expected Callback`() = runAndroidComposeUiTest { val result = aResolvedRoomAlias() val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(result) { - rule.setRoomAliasResolverView( + setRoomAliasResolverView( aRoomAliasResolverState( resolveState = AsyncData.Success(result), eventSink = eventsRecorder, @@ -74,7 +73,7 @@ class RoomAliasHelperViewTest { } } -private fun AndroidComposeTestRule.setRoomAliasResolverView( +private fun AndroidComposeUiTest.setRoomAliasResolverView( state: RoomAliasResolverState, onBackClick: () -> Unit = EnsureNeverCalled(), onAliasResolved: (ResolvedRoomAlias) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index 588a10a218..50139e0149 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.roomdetails.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.userprofile.shared.aUserProfileState @@ -32,98 +35,94 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class RoomDetailsViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `click on back invokes expected callback`() { + fun `click on back invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( goBack = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `click on share invokes expected callback`() { + fun `click on share invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( onShareRoom = callback, ) - rule.clickOn(CommonStrings.action_share) + clickOn(CommonStrings.action_share) } } @Config(qualifiers = "h1024dp") @Test - fun `click on room members invokes expected callback`() { + fun `click on room members invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( openRoomMemberList = callback, ) - rule.clickOn(CommonStrings.common_people) + clickOn(CommonStrings.common_people) } } @Config(qualifiers = "h1024dp") @Test - fun `click on polls invokes expected callback`() { + fun `click on polls invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( openPollHistory = callback, ) - rule.clickOn(R.string.screen_polls_history_title) + clickOn(R.string.screen_polls_history_title) } } @Config(qualifiers = "h1024dp") @Test - fun `click on media gallery invokes expected callback`() { + fun `click on media gallery invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( openMediaGallery = callback, ) - rule.clickOn(R.string.screen_room_details_media_gallery_title) + clickOn(R.string.screen_room_details_media_gallery_title) } } @Config(qualifiers = "h1024dp") @Test - fun `click on notification invokes expected callback`() { + fun `click on notification invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( openRoomNotificationSettings = callback, ) - rule.clickOn(R.string.screen_room_details_notification_title) + clickOn(R.string.screen_room_details_notification_title) } } @Test - fun `click on invite invokes expected callback`() { + fun `click on invite invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canInvite = true, ), invitePeople = callback, ) - rule.clickOn(CommonStrings.action_invite) + clickOn(CommonStrings.action_invite) } } @Test - fun `click on call invokes expected callback`() { + fun `click on call invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(CallIntent.AUDIO) { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canInvite = true, @@ -134,103 +133,103 @@ class RoomDetailsViewTest { ), onJoinCallClick = callback, ) - rule.clickOn(CommonStrings.action_call) + clickOn(CommonStrings.action_call) } } @Test - fun `click on video call invokes expected callback`() { + fun `click on video call invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(CallIntent.VIDEO) { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canInvite = true, ), onJoinCallClick = callback, ) - rule.clickOn(CommonStrings.common_video) + clickOn(CommonStrings.common_video) } } @Config(qualifiers = "h1024dp") @Test - fun `click on pinned messages invokes expected callback`() { + fun `click on pinned messages invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canInvite = true, ), onPinnedMessagesClick = callback, ) - rule.clickOn(R.string.screen_room_details_pinned_events_row_title) + clickOn(R.string.screen_room_details_pinned_events_row_title) } } @Config(qualifiers = "h1024dp") @Test - fun `click on security and privacy invokes expected callback`() { + fun `click on security and privacy invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canShowSecurityAndPrivacy = true, ), onSecurityAndPrivacyClick = callback, ) - rule.clickOn(R.string.screen_room_details_security_and_privacy_title) + clickOn(R.string.screen_room_details_security_and_privacy_title) } } @Config(qualifiers = "h1024dp") @Test - fun `click on add topic emit expected event`() { + fun `click on add topic emit expected event`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(RoomDetailsAction.AddTopic) { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), roomTopic = RoomTopicState.CanAddTopic, ), onActionClick = callback, ) - rule.clickOn(R.string.screen_room_details_add_topic_title) + clickOn(R.string.screen_room_details_add_topic_title) } } @Test - fun `click on menu edit emit expected event`() { + fun `click on menu edit emit expected event`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(RoomDetailsAction.Edit) { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canEdit = true, ), onActionClick = callback, ) - val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) - rule.onNodeWithContentDescription(menuContentDescription).performClick() - rule.clickOn(CommonStrings.action_edit) + val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) + onNodeWithContentDescription(menuContentDescription).performClick() + clickOn(CommonStrings.action_edit) } } @Test - fun `click on avatar test`() { + fun `click on avatar test`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aRoomDetailsState( eventSink = eventsRecorder, roomAvatarUrl = "an_avatar_url", ) val callback = EnsureCalledOnceWithTwoParams(state.roomName, "an_avatar_url") - rule.setRoomDetailView( + setRoomDetailView( state = state, openAvatarPreview = callback, ) - rule.onNodeWithTag(TestTags.roomDetailAvatar.value).performClick() + onNodeWithTag(TestTags.roomDetailAvatar.value).performClick() callback.assertSuccess() } @Test - fun `click on avatar test on DM`() { + fun `click on avatar test on DM`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) val state = aRoomDetailsState( roomType = RoomDetailsType.Dm( @@ -241,114 +240,114 @@ class RoomDetailsViewTest { eventSink = eventsRecorder, ) val callback = EnsureCalledOnceWithTwoParams("Daniel", "an_avatar_url") - rule.setRoomDetailView( + setRoomDetailView( state = state, openAvatarPreview = callback, ) - rule.onNodeWithTag(TestTags.memberDetailAvatar.value).performClick() + onNodeWithTag(TestTags.memberDetailAvatar.value).performClick() callback.assertSuccess() } @Test - fun `click on mute emit expected event`() { + fun `click on mute emit expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomDetailsState( eventSink = eventsRecorder, roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.ALL_MESSAGES), ) - rule.setRoomDetailView( + setRoomDetailView( state = state, ) - rule.clickOn(CommonStrings.common_mute) + clickOn(CommonStrings.common_mute) eventsRecorder.assertSingle(RoomDetailsEvent.MuteNotification) } @Test - fun `click on unmute emit expected event`() { + fun `click on unmute emit expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomDetailsState( eventSink = eventsRecorder, roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.MUTE), ) - rule.setRoomDetailView( + setRoomDetailView( state = state, ) - rule.clickOn(CommonStrings.common_unmute) + clickOn(CommonStrings.common_unmute) eventsRecorder.assertSingle(RoomDetailsEvent.UnmuteNotification) } @Config(qualifiers = "h1024dp") @Test - fun `click on favorite emit expected Event`() { + fun `click on favorite emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.common_favourite) + clickOn(CommonStrings.common_favourite) eventsRecorder.assertSingle(RoomDetailsEvent.SetFavorite(true)) } @Config(qualifiers = "h1500dp") @Test - fun `click on leave emit expected Event`() { + fun `click on leave emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_room_details_leave_room_title) + clickOn(R.string.screen_room_details_leave_room_title) eventsRecorder.assertSingle(RoomDetailsEvent.LeaveRoom(needsConfirmation = true)) } @Config(qualifiers = "h1500dp") @Test - fun `click on report room invokes expected callback`() { + fun `click on report room invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), ), onReportRoomClick = callback, ) - rule.clickOn(CommonStrings.action_report_room) + clickOn(CommonStrings.action_report_room) } } @Config(qualifiers = "h1024dp") @Test - fun `click on knock requests invokes expected callback`() { + fun `click on knock requests invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), canShowKnockRequests = true, ), onKnockRequestsClick = callback, ) - rule.clickOn(R.string.screen_room_details_requests_to_join_title) + clickOn(R.string.screen_room_details_requests_to_join_title) } } @Config(qualifiers = "h1024dp") @Test - fun `click on profile invokes the expected callback`() { + fun `click on profile invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(A_USER_ID) { callback -> - rule.setRoomDetailView( + setRoomDetailView( state = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), roomMemberDetailsState = aUserProfileState(userId = A_USER_ID), ), onProfileClick = callback, ) - rule.clickOn(R.string.screen_room_details_profile_row_title) + clickOn(R.string.screen_room_details_profile_row_title) } } } -private fun AndroidComposeTestRule.setRoomDetailView( +private fun AndroidComposeUiTest.setRoomDetailView( state: RoomDetailsState = aRoomDetailsState( eventSink = EventsRecorder(expectEvents = false), ), diff --git a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt index 71fb143074..686794d641 100644 --- a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt @@ -5,18 +5,21 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.roomdetailsedit.impl import androidx.activity.ComponentActivity import androidx.annotation.StringRes +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assert import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.isEditable -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -28,58 +31,54 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import org.junit.Ignore -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomDetailsEditViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back emits the expected Event`() { + fun `clicking on back emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder ), ) - rule.pressBack() + pressBack() eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) } @Test - fun `clicking on discard when confirming exit emits the expected Event`() { + fun `clicking on discard when confirming exit emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_discard) + clickOn(CommonStrings.action_discard) eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) } @Test - fun `clicking on save when confirming exit emits the expected Event`() { + fun `clicking on save when confirming exit emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save, inDialog = true) + clickOn(CommonStrings.action_save, inDialog = true) eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test - fun `when edition is successful, the expected callback is invoked`() { + fun `when edition is successful, the expected callback is invoked`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, saveAction = AsyncAction.Success(Unit) @@ -90,55 +89,55 @@ class RoomDetailsEditViewTest { } @Test - fun `when name is changed, the expected Event is emitted`() { + fun `when name is changed, the expected Event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, roomRawName = "Marketing", ), ) - rule.onNodeWithText("Marketing").performTextInput("A") + onNodeWithText("Marketing").performTextInput("A") eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomName("AMarketing")) } @Test - fun `when user cannot change name, nothing happen`() { + fun `when user cannot change name, nothing happen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, roomRawName = "Marketing", canChangeName = false, ), ) - rule.onNodeWithText("Marketing").assert(!isEditable()) + onNodeWithText("Marketing").assert(!isEditable()) } @Test - fun `when topic is changed, the expected Event is emitted`() { + fun `when topic is changed, the expected Event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, roomTopic = "My Topic", ), ) - rule.onNodeWithText("My Topic").performTextInput("A") + onNodeWithText("My Topic").performTextInput("A") eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomTopic("AMy Topic")) } @Test - fun `when user cannot change topic, nothing happen`() { + fun `when user cannot change topic, nothing happen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, roomTopic = "My Topic", canChangeTopic = false, ), ) - rule.onNodeWithText("My Topic").assert(!isEditable()) + onNodeWithText("My Topic").assert(!isEditable()) } @Ignore("This test is failing because the bottom sheet does not open") @@ -171,73 +170,73 @@ class RoomDetailsEditViewTest { private fun testAvatarChange( @StringRes stringActionRes: Int, expectedEvent: RoomDetailsEditEvent.HandleAvatarAction, - ) { + ) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, ), ) // Open the bottom sheet - rule.onNode(hasTestTag(TestTags.editAvatar.value)).performClick() - rule.onNodeWithText(rule.activity.getString(stringActionRes)).assertExists() - rule.clickOn(stringActionRes) + onNode(hasTestTag(TestTags.editAvatar.value)).performClick() + onNodeWithText(activity!!.getString(stringActionRes)).assertExists() + clickOn(stringActionRes) eventsRecorder.assertSingle(expectedEvent) } @Test - fun `when user cannot change avatar, nothing happen`() { + fun `when user cannot change avatar, nothing happen`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, canChangeAvatar = false, ), ) - rule.onNode(hasTestTag(TestTags.editAvatar.value)).performClick() - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_take_photo)).assertDoesNotExist() + onNode(hasTestTag(TestTags.editAvatar.value)).performClick() + onNodeWithText(activity!!.getString(CommonStrings.action_take_photo)).assertDoesNotExist() } @Test - fun `when save is clicked, the expected Event is emitted`() { + fun `when save is clicked, the expected Event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, saveButtonEnabled = true, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test - fun `when save is clicked, but nothing need to be saved, nothing happens`() { + fun `when save is clicked, but nothing need to be saved, nothing happens`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, saveButtonEnabled = false, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) } @Test - fun `when error is shown, closing the dialog emit the expected Event`() { + fun `when error is shown, closing the dialog emit the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDetailsEditView( + setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, saveAction = AsyncAction.Failure(RuntimeException("Whelp")), ), ) - rule.clickOn(CommonStrings.action_ok) + clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle(RoomDetailsEditEvent.CloseDialog) } } -private fun AndroidComposeTestRule.setRoomDetailsEditView( +private fun AndroidComposeUiTest.setRoomDetailsEditView( state: RoomDetailsEditState, onDone: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt index a50ad6a22c..f9d60f87da 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -6,15 +6,18 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.roomdirectory.impl.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.testtags.TestTags @@ -22,31 +25,27 @@ import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.ensureCalledOnceWithParam -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomDirectoryViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `typing text in search field emits the expected Event`() { + fun `typing text in search field emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomDirectoryView( + setRoomDirectoryView( state = aRoomDirectoryState( eventSink = eventsRecorder, ) ) - rule.onNodeWithTag(TestTags.searchTextField.value).performTextInput( + onNodeWithTag(TestTags.searchTextField.value).performTextInput( text = "Test" ) eventsRecorder.assertSingle(RoomDirectoryEvents.Search("Test")) } @Test - fun `clicking on room item then onResultClick lambda is called once`() { + fun `clicking on room item then onResultClick lambda is called once`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomDirectoryState( roomDescriptions = aRoomDescriptionList(), @@ -54,27 +53,27 @@ class RoomDirectoryViewTest { ) val clickedRoom = state.roomDescriptions.first() ensureCalledOnceWithParam(clickedRoom) { callback -> - rule.setRoomDirectoryView( + setRoomDirectoryView( state = state, onResultClick = callback, ) - rule.onNodeWithText(clickedRoom.computedName).performClick() + onNodeWithText(clickedRoom.computedName).performClick() } } @Test - fun `composing load more indicator emits expected Event`() { + fun `composing load more indicator emits expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aRoomDirectoryState( displayLoadMoreIndicator = true, eventSink = eventsRecorder, ) - rule.setRoomDirectoryView(state = state) + setRoomDirectoryView(state = state) eventsRecorder.assertSingle(RoomDirectoryEvents.LoadMore) } } -private fun AndroidComposeTestRule.setRoomDirectoryView( +private fun AndroidComposeUiTest.setRoomDirectoryView( state: RoomDirectoryState, onBackClick: () -> Unit = EnsureNeverCalled(), onResultClick: (RoomDescription) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt index 6508b28053..646481715a 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.roommembermoderation.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState @@ -24,21 +27,17 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnceWithTwoParams import io.element.android.tests.testutils.pressTag import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RoomMemberModerationViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on display profile action calls onSelectAction`() { + fun `clicking on display profile action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.DisplayProfile, user) { callback -> - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = user, actions = listOf( @@ -48,16 +47,16 @@ class RoomMemberModerationViewTest { ), onSelectAction = callback ) - rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_member_user_info) + clickOn(R.string.screen_bottom_sheet_manage_room_member_member_user_info) } } @Test - fun `clicking on kick user action calls onSelectAction`() { + fun `clicking on kick user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.KickUser, user) { callback -> - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = user, actions = listOf( @@ -67,18 +66,18 @@ class RoomMemberModerationViewTest { ), onSelectAction = callback ) - rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) + clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) // Gives time for bottomsheet to hide - rule.mainClock.advanceTimeBy(1_000) + mainClock.advanceTimeBy(1_000) } } @Test - fun `clicking on ban user action calls onSelectAction`() { + fun `clicking on ban user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.BanUser, user) { callback -> - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = user, actions = listOf( @@ -88,18 +87,18 @@ class RoomMemberModerationViewTest { ), onSelectAction = callback ) - rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_ban) + clickOn(R.string.screen_bottom_sheet_manage_room_member_ban) // Gives time for bottomsheet to hide - rule.mainClock.advanceTimeBy(1_000) + mainClock.advanceTimeBy(1_000) } } @Test - fun `clicking on unban user action calls onSelectAction`() { + fun `clicking on unban user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.UnbanUser, user) { callback -> - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = user, actions = listOf( @@ -109,100 +108,100 @@ class RoomMemberModerationViewTest { ), onSelectAction = callback ) - rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_unban) + clickOn(R.string.screen_bottom_sheet_manage_room_member_unban) // Gives time for bottomsheet to hide - rule.mainClock.advanceTimeBy(1_000) + mainClock.advanceTimeBy(1_000) } } @Test - fun `clicking submit on kick confirmation dialog sends DoKickUser event`() { + fun `clicking submit on kick confirmation dialog sends DoKickUser event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), kickUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoKickUser(reason = "")) } @Test - fun `clicking dismiss on kick confirmation dialog sends Reset event`() { + fun `clicking dismiss on kick confirmation dialog sends Reset event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), kickUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogNegative.value) + pressTag(TestTags.dialogNegative.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) } @Test - fun `clicking submit on ban confirmation dialog sends DoBanUser event`() { + fun `clicking submit on ban confirmation dialog sends DoBanUser event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), banUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoBanUser(reason = "")) } @Test - fun `clicking dismiss on ban confirmation dialog sends Reset event`() { + fun `clicking dismiss on ban confirmation dialog sends Reset event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), banUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogNegative.value) + pressTag(TestTags.dialogNegative.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) } @Test - fun `clicking confirm on unban confirmation dialog sends DoUnbanUser event`() { + fun `clicking confirm on unban confirmation dialog sends DoUnbanUser event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), unbanUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogPositive.value) + pressTag(TestTags.dialogPositive.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoUnbanUser("")) } @Test - fun `clicking dismiss on unban confirmation dialog sends Reset event`() { + fun `clicking dismiss on unban confirmation dialog sends Reset event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), unbanUserAsyncAction = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder ), ) - rule.pressTag(TestTags.dialogNegative.value) + pressTag(TestTags.dialogNegative.value) eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) } @Test - fun `disabled actions are not clickable`() { + fun `disabled actions are not clickable`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setRoomMemberModerationView( + setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), actions = listOf( @@ -211,11 +210,11 @@ class RoomMemberModerationViewTest { eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) + clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) } } -private fun AndroidComposeTestRule.setRoomMemberModerationView( +private fun AndroidComposeUiTest.setRoomMemberModerationView( state: InternalRoomMemberModerationState, onSelectAction: (ModerationAction, MatrixUser) -> Unit = EnsureNeverCalledWithTwoParams(), ) { diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt index d9324fdb91..f9729f74f0 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt @@ -6,16 +6,19 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securebackup.impl.enter import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performImeAction import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.securebackup.impl.setup.views.aFormattedRecoveryKey import io.element.android.libraries.architecture.AsyncAction @@ -26,58 +29,54 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class SecureBackupEnterRecoveryKeyViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `back key pressed - calls onBackClick`() { + fun `back key pressed - calls onBackClick`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(), onBackClick = callback, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `back button clicked - calls onBackClick`() { + fun `back button clicked - calls onBackClick`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(), onBackClick = callback, ) - rule.pressBack() + pressBack() } } @Test @Config(qualifiers = "h1024dp") - fun `tapping on Continue when key is valid - calls expected action`() { + fun `tapping on Continue when key is valid - calls expected action`() = runAndroidComposeUiTest { val recorder = EventsRecorder() - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit) } @Test - fun `entering a char emits the expected event`() { + fun `entering a char emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) - rule.onNodeWithText(keyValue).performTextInput("X") + onNodeWithText(keyValue).performTextInput("X") recorder.assertSingle( SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange("X$keyValue") ) @@ -85,43 +84,43 @@ class SecureBackupEnterRecoveryKeyViewTest { @Test @Config(qualifiers = "h1024dp") - fun `toggling the visibility of the textfield changes it`() { + fun `toggling the visibility of the textfield changes it`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() - rule.setSecureBackupEnterRecoveryKeyView(aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder)) + setSecureBackupEnterRecoveryKeyView(aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder)) // Initially, the text field should be visible - rule.onNodeWithText(keyValue).assertExists() + onNodeWithText(keyValue).assertExists() - rule.onNodeWithContentDescription(rule.activity.getString(CommonStrings.a11y_hide_password)).performClick() + onNodeWithContentDescription(activity!!.getString(CommonStrings.a11y_hide_password)).performClick() - rule.waitForIdle() + waitForIdle() recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility(false)) } @Test - fun `validating from keyboard emits the expected event`() { + fun `validating from keyboard emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) - rule.onNodeWithText(keyValue).performImeAction() + onNodeWithText(keyValue).performImeAction() recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit) } @Test - fun `when submit action succeeds - calls onDone`() { + fun `when submit action succeeds - calls onDone`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setSecureBackupEnterRecoveryKeyView( + setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Success(Unit)), onDone = callback, ) } } - private fun AndroidComposeTestRule.setSecureBackupEnterRecoveryKeyView( + private fun AndroidComposeUiTest.setSecureBackupEnterRecoveryKeyView( state: SecureBackupEnterRecoveryKeyState, onDone: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt index 6cfd061103..ce5972f66b 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securebackup.impl.reset.password import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.ui.strings.CommonStrings @@ -22,64 +25,59 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ResetIdentityPasswordViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `pressing the back HW button invokes the expected callback`() { + fun `pressing the back HW button invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setResetPasswordView( + setResetPasswordView( ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}), onBack = it, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `clicking on the back navigation button invokes the expected callback`() { + fun `clicking on the back navigation button invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setResetPasswordView( + setResetPasswordView( ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}), onBack = it, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking 'Reset identity' confirms the reset`() { + fun `clicking 'Reset identity' confirms the reset`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResetPasswordView( + setResetPasswordView( ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = eventsRecorder), ) - rule.onNodeWithText("Password").performTextInput("A password") + onNodeWithText("Password").performTextInput("A password") - rule.clickOn(CommonStrings.action_reset_identity) + clickOn(CommonStrings.action_reset_identity) eventsRecorder.assertSingle(ResetIdentityPasswordEvent.Reset("A password")) } @Test - fun `modifying the password dismisses the error state`() { + fun `modifying the password dismisses the error state`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResetPasswordView( + setResetPasswordView( ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder), ) - rule.onNodeWithText("Password").performTextInput("A password") + onNodeWithText("Password").performTextInput("A password") eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError) } } -private fun AndroidComposeTestRule.setResetPasswordView( +private fun AndroidComposeUiTest.setResetPasswordView( state: ResetIdentityPasswordState, onBack: () -> Unit = EnsureNeverCalled(), ) { diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt index a913a9af27..0126d0d879 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securebackup.impl.reset.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.securebackup.impl.R import io.element.android.libraries.ui.strings.CommonStrings @@ -20,76 +23,71 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class ResetIdentityRootViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `pressing the back HW button invokes the expected callback`() { + fun `pressing the back HW button invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setResetRootView( + setResetRootView( ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}), onBack = it, ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `clicking on the back navigation button invokes the expected callback`() { + fun `clicking on the back navigation button invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setResetRootView( + setResetRootView( ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}), onBack = it, ) - rule.pressBack() + pressBack() } } @Test @Config(qualifiers = "h720dp") - fun `clicking Continue displays the confirmation dialog`() { + fun `clicking Continue displays the confirmation dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResetRootView( + setResetRootView( ResetIdentityRootState(displayConfirmationDialog = false, eventSink = eventsRecorder), ) - rule.clickOn(R.string.screen_encryption_reset_action_continue_reset) + clickOn(R.string.screen_encryption_reset_action_continue_reset) eventsRecorder.assertSingle(ResetIdentityRootEvent.Continue) } @Test - fun `clicking 'Yes, reset now' confirms the reset`() { + fun `clicking 'Yes, reset now' confirms the reset`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setResetRootView( + setResetRootView( ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}), onContinue = it, ) - rule.clickOn(R.string.screen_reset_encryption_confirmation_alert_action) + clickOn(R.string.screen_reset_encryption_confirmation_alert_action) } } @Test - fun `clicking Cancel dismisses the dialog`() { + fun `clicking Cancel dismisses the dialog`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setResetRootView( + setResetRootView( ResetIdentityRootState(displayConfirmationDialog = true, eventSink = eventsRecorder), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(ResetIdentityRootEvent.DismissDialog) } } -private fun AndroidComposeTestRule.setResetRootView( +private fun AndroidComposeUiTest.setResetRootView( state: ResetIdentityRootState, onBack: () -> Unit = EnsureNeverCalled(), onContinue: () -> Unit = EnsureNeverCalled(), diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt index 17d6f3a88d..2c0a6c9acb 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity @@ -23,86 +26,82 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EditRoomAddressViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `click on back invokes expected callback`() { + fun `click on back invokes expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setEditRoomAddressView(onBackClick = callback) - rule.pressBack() + setEditRoomAddressView(onBackClick = callback) + pressBack() } } @Test - fun `click on disabled save doesn't emit event`() { + fun `click on disabled save doesn't emit event`() = runAndroidComposeUiTest { val recorder = EventsRecorder(expectEvents = false) val state = anEditRoomAddressState(eventSink = recorder) - rule.setEditRoomAddressView(state) - rule.clickOn(CommonStrings.action_save) + setEditRoomAddressView(state) + clickOn(CommonStrings.action_save) recorder.assertEmpty() } @Test - fun `click on enabled save emits the expected event`() { + fun `click on enabled save emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "room", roomAddressValidity = RoomAddressValidity.Valid, eventSink = recorder ) - rule.setEditRoomAddressView(state) - rule.clickOn(CommonStrings.action_save) + setEditRoomAddressView(state) + clickOn(CommonStrings.action_save) recorder.assertSingle(EditRoomAddressEvents.Save) } @Test - fun `text changes on text field emits the expected event`() { + fun `text changes on text field emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", eventSink = recorder ) - rule.setEditRoomAddressView(state) + setEditRoomAddressView(state) - rule.onNodeWithTag(TestTags.roomAddressField.value).performTextInput("alias") + onNodeWithTag(TestTags.roomAddressField.value).performTextInput("alias") recorder.assertSingle(EditRoomAddressEvents.RoomAddressChanged("alias")) } @Test - fun `click on dismiss error emits the expected event`() { + fun `click on dismiss error emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", saveAction = AsyncAction.Failure(IllegalStateException()), eventSink = recorder ) - rule.setEditRoomAddressView(state) - rule.clickOn(CommonStrings.action_cancel) + setEditRoomAddressView(state) + clickOn(CommonStrings.action_cancel) recorder.assertSingle(EditRoomAddressEvents.DismissError) } @Test - fun `click on retry error emits the expected event`() { + fun `click on retry error emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", saveAction = AsyncAction.Failure(IllegalStateException()), eventSink = recorder ) - rule.setEditRoomAddressView(state) - rule.clickOn(CommonStrings.action_retry) + setEditRoomAddressView(state) + clickOn(CommonStrings.action_retry) recorder.assertSingle(EditRoomAddressEvents.Save) } } -private fun AndroidComposeTestRule.setEditRoomAddressView( +private fun AndroidComposeUiTest.setEditRoomAddressView( state: EditRoomAddressState = anEditRoomAddressState( eventSink = EventsRecorder(expectEvents = false), ), diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/manageauthorizedspaces/ManageAuthorizedSpacesViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/manageauthorizedspaces/ManageAuthorizedSpacesViewTest.kt index c732df6df0..de6da41823 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/manageauthorizedspaces/ManageAuthorizedSpacesViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/manageauthorizedspaces/ManageAuthorizedSpacesViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom @@ -24,26 +27,22 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ManageAuthorizedSpacesViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking back emits Cancel event`() { + fun `clicking back emits Cancel event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aManageAuthorizedSpacesState(eventSink = recorder) - rule.setManageAuthorizedSpacesView(state) - rule.pressBack() + setManageAuthorizedSpacesView(state) + pressBack() recorder.assertSingle(ManageAuthorizedSpacesEvent.Cancel) } @Test - fun `clicking space checkbox emits ToggleSpace event`() { + fun `clicking space checkbox emits ToggleSpace event`() = runAndroidComposeUiTest { val roomId = A_ROOM_ID val space = aSpaceRoom(roomId = roomId, displayName = "Test Space") val recorder = EventsRecorder() @@ -51,37 +50,37 @@ class ManageAuthorizedSpacesViewTest { selectableSpaces = listOf(space), eventSink = recorder ) - rule.setManageAuthorizedSpacesView(state) - rule.onNodeWithText("Test Space").performClick() + setManageAuthorizedSpacesView(state) + onNodeWithText("Test Space").performClick() recorder.assertSingle(ManageAuthorizedSpacesEvent.ToggleSpace(roomId)) } @Test - fun `clicking done button emits Done event`() { + fun `clicking done button emits Done event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aManageAuthorizedSpacesState( selectedIds = listOf(A_ROOM_ID), eventSink = recorder ) - rule.setManageAuthorizedSpacesView(state) - rule.clickOn(CommonStrings.action_done) + setManageAuthorizedSpacesView(state) + clickOn(CommonStrings.action_done) recorder.assertSingle(ManageAuthorizedSpacesEvent.Done) } @Test - fun `done button is disabled when no spaces selected`() { + fun `done button is disabled when no spaces selected`() = runAndroidComposeUiTest { val recorder = EventsRecorder(expectEvents = false) val state = aManageAuthorizedSpacesState( selectedIds = emptyList(), eventSink = recorder ) - rule.setManageAuthorizedSpacesView(state) - rule.clickOn(CommonStrings.action_done) + setManageAuthorizedSpacesView(state) + clickOn(CommonStrings.action_done) recorder.assertEmpty() } } -private fun AndroidComposeTestRule.setManageAuthorizedSpacesView( +private fun AndroidComposeUiTest.setManageAuthorizedSpacesView( state: ManageAuthorizedSpacesState = aManageAuthorizedSpacesState( eventSink = EventsRecorder(expectEvents = false) ), diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyViewTest.kt index a1f46b2938..c46accbc91 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyViewTest.kt @@ -5,13 +5,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.securityandprivacy.impl.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.securityandprivacy.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -23,73 +26,69 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.persistentListOf -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class SecurityAndPrivacyViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `click on back invokes emits the expected event`() { + fun `click on back invokes emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, ) - rule.setSecurityAndPrivacyView(state) - rule.pressBack() + setSecurityAndPrivacyView(state) + pressBack() recorder.assertSingle(SecurityAndPrivacyEvent.Exit) } @Test - fun `discard cancellation emits the expected event`() { + fun `discard cancellation emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_discard) + setSecurityAndPrivacyView(state) + clickOn(CommonStrings.action_discard) recorder.assertSingle(SecurityAndPrivacyEvent.Exit) } @Test - fun `save cancellation confirmation emits the expected event`() { + fun `save cancellation confirmation emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_save, inDialog = true) + setSecurityAndPrivacyView(state) + clickOn(CommonStrings.action_save, inDialog = true) recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test - fun `click on room access item emits the expected event`() { + fun `click on room access item emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_room_access_invite_only_option_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_room_access_invite_only_option_title) recorder.assertSingle(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) } @Test - fun `click on disabled save doesn't emit event`() { + fun `click on disabled save doesn't emit event`() = runAndroidComposeUiTest { val recorder = EventsRecorder(expectEvents = false) val state = aSecurityAndPrivacyState(eventSink = recorder) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_save) + setSecurityAndPrivacyView(state) + clickOn(CommonStrings.action_save) recorder.assertEmpty() } @Test - fun `click on enabled save emits the expected event`() { + fun `click on enabled save emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, @@ -97,14 +96,14 @@ class SecurityAndPrivacyViewTest { roomAccess = SecurityAndPrivacyRoomAccess.Anyone, ) ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_save) + setSecurityAndPrivacyView(state) + clickOn(CommonStrings.action_save) recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test @Config(qualifiers = "h640dp") - fun `click on room address item emits the expected event`() { + fun `click on room address item emits the expected event`() = runAndroidComposeUiTest { val address = "@alias:matrix.org" val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( @@ -114,14 +113,14 @@ class SecurityAndPrivacyViewTest { roomAccess = SecurityAndPrivacyRoomAccess.Anyone, ), ) - rule.setSecurityAndPrivacyView(state) - rule.onNodeWithText(address).performClick() + setSecurityAndPrivacyView(state) + onNodeWithText(address).performClick() recorder.assertSingle(SecurityAndPrivacyEvent.EditRoomAddress) } @Test @Config(qualifiers = "h1024dp") - fun `click on room visibility item emits the expected event`() { + fun `click on room visibility item emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, @@ -130,14 +129,14 @@ class SecurityAndPrivacyViewTest { isVisibleInRoomDirectory = AsyncData.Success(false), ), ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_room_directory_visibility_toggle_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_room_directory_visibility_toggle_title) recorder.assertSingle(SecurityAndPrivacyEvent.ToggleRoomVisibility) } @Test @Config(qualifiers = "h1024dp") - fun `click on history visibility item emits the expected event`() { + fun `click on history visibility item emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, @@ -145,65 +144,65 @@ class SecurityAndPrivacyViewTest { historyVisibility = SecurityAndPrivacyHistoryVisibility.Invited, ), ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_room_history_since_invite_option_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_room_history_since_invite_option_title) recorder.assertSingle(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Invited)) } @Test @Config(qualifiers = "h1024dp") - fun `click on encryption item emits the expected event`() { + fun `click on encryption item emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, savedSettings = aSecurityAndPrivacySettings(isEncrypted = false), ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_encryption_toggle_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_encryption_toggle_title) recorder.assertSingle(SecurityAndPrivacyEvent.ToggleEncryptionState) } @Test - fun `click on encryption confirm emits the expected event`() { + fun `click on encryption confirm emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, showEncryptionConfirmation = true, ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_enable_encryption_alert_confirm_button_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_enable_encryption_alert_confirm_button_title) recorder.assertSingle(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } @Test @Config(qualifiers = "h1024dp") - fun `click on space member access emits the expected event`() { + fun `click on space member access emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, spaceSelectionMode = SpaceSelectionMode.Single(A_ROOM_ID, null), ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_room_access_space_members_option_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_room_access_space_members_option_title) recorder.assertSingle(SecurityAndPrivacyEvent.SelectSpaceMemberAccess) } @Test @Config(qualifiers = "h1024dp") - fun `click on ask to join with space members emits the expected event`() { + fun `click on ask to join with space members emits the expected event`() = runAndroidComposeUiTest { val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, spaceSelectionMode = SpaceSelectionMode.Single(A_ROOM_ID, null), ) - rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_ask_to_join_option_title) + setSecurityAndPrivacyView(state) + clickOn(R.string.screen_security_and_privacy_ask_to_join_option_title) recorder.assertSingle(SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess) } @Test @Config(qualifiers = "h1024dp") - fun `manage spaces footer is shown when space member access is selected`() { + fun `manage spaces footer is shown when space member access is selected`() = runAndroidComposeUiTest { val recorder = EventsRecorder(expectEvents = false) val state = aSecurityAndPrivacyState( eventSink = recorder, @@ -212,15 +211,16 @@ class SecurityAndPrivacyViewTest { roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(persistentListOf(A_ROOM_ID)), ), ) - rule.setSecurityAndPrivacyView(state) + setSecurityAndPrivacyView(state) // The footer text uses AnnotatedString with a link. Verify the footer text is displayed. - val actionFooterText = rule.activity.getString(R.string.screen_security_and_privacy_room_access_footer_manage_spaces_action) - val footerText = rule.activity.getString(R.string.screen_security_and_privacy_room_access_footer, actionFooterText) - rule.onNodeWithText(footerText).assertExists() + val resources = activity!!.resources + val actionFooterText = resources.getString(R.string.screen_security_and_privacy_room_access_footer_manage_spaces_action) + val footerText = resources.getString(R.string.screen_security_and_privacy_room_access_footer, actionFooterText) + onNodeWithText(footerText).assertExists() } } -private fun AndroidComposeTestRule.setSecurityAndPrivacyView( +private fun AndroidComposeUiTest.setSecurityAndPrivacyView( state: SecurityAndPrivacyState = aSecurityAndPrivacyState( eventSink = EventsRecorder(expectEvents = false), ), diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt index d75fecd05a..6fc10f1e82 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt @@ -5,13 +5,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.space.impl.addroom import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -22,77 +25,73 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.toImmutableList -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class AddRoomToSpaceViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking back when search inactive emits Dismiss and invokes onBackClick`() { + fun `clicking back when search inactive emits Dismiss and invokes onBackClick`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( isSearchActive = false, eventSink = eventsRecorder, ), onBackClick = it, ) - rule.pressBack() + pressBack() } eventsRecorder.assertSingle(AddRoomToSpaceEvent.Dismiss) } @Test - fun `clicking back when search active emits CloseSearch event`() { + fun `clicking back when search active emits CloseSearch event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( isSearchActive = true, eventSink = eventsRecorder, ), ) - rule.pressBack() + pressBack() eventsRecorder.assertSingle(AddRoomToSpaceEvent.OnSearchActiveChanged(false)) } @Test - fun `clicking save emits Save event`() { + fun `clicking save emits Save event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( selectedRooms = aSelectRoomInfoList().take(1).toImmutableList(), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) + clickOn(CommonStrings.action_save) eventsRecorder.assertSingle(AddRoomToSpaceEvent.Save) } @Config(qualifiers = "h1024dp") @Test - fun `clicking room in suggestions emits ToggleRoom event`() { + fun `clicking room in suggestions emits ToggleRoom event`() = runAndroidComposeUiTest { val suggestions = aSelectRoomInfoList() val eventsRecorder = EventsRecorder() - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( suggestions = suggestions, eventSink = eventsRecorder, ), ) - rule.onNodeWithText(suggestions.first().name!!).performClick() + onNodeWithText(suggestions.first().name!!).performClick() eventsRecorder.assertSingle(AddRoomToSpaceEvent.ToggleRoom(suggestions.first())) } @Test - fun `onRoomsAdded called when saveAction is Success`() { + fun `onRoomsAdded called when saveAction is Success`() = runAndroidComposeUiTest { ensureCalledOnce { - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( saveAction = AsyncAction.Success(Unit), ), @@ -103,10 +102,10 @@ class AddRoomToSpaceViewTest { @Config(qualifiers = "h1024dp") @Test - fun `displaying search results sends UpdateSearchVisibleRange event`() { + fun `displaying search results sends UpdateSearchVisibleRange event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val rooms = aSelectRoomInfoList() - rule.setAddRoomToSpaceView( + setAddRoomToSpaceView( anAddRoomToSpaceState( isSearchActive = true, searchResults = SearchBarResultState.Results(rooms), @@ -117,7 +116,7 @@ class AddRoomToSpaceViewTest { } } -private fun AndroidComposeTestRule.setAddRoomToSpaceView( +private fun AndroidComposeUiTest.setAddRoomToSpaceView( state: AddRoomToSpaceState, onBackClick: () -> Unit = EnsureNeverCalled(), onRoomsAdded: () -> Unit = EnsureNeverCalled(), diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt index 87343b6e34..6632c7f4f8 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.space.impl.root import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -33,37 +36,33 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class SpaceViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setSpaceView( + setSpaceView( aSpaceState( hasMoreToLoad = false, eventSink = eventsRecorder, ), onBackClick = it, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on a room name invokes the expected callback`() { + fun `clicking on a room name invokes the expected callback`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME) val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(aSpaceRoom) { - rule.setSpaceView( + setSpaceView( aSpaceState( children = listOf(aSpaceRoom), hasMoreToLoad = false, @@ -71,91 +70,91 @@ class SpaceViewTest { ), onRoomClick = it, ) - rule.onNodeWithText(A_ROOM_NAME).performClick() + onNodeWithText(A_ROOM_NAME).performClick() } } @Test - fun `clicking on Join room emits the expected Event`() { + fun `clicking on Join room emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = null) val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( children = listOf(aSpaceRoom), hasMoreToLoad = false, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_join) + clickOn(CommonStrings.action_join) eventsRecorder.assertSingle(SpaceEvents.Join(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on accept invite emits the expected Event`() { + fun `clicking on accept invite emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED) val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( hasMoreToLoad = false, children = listOf(aSpaceRoom), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_accept) + clickOn(CommonStrings.action_accept) eventsRecorder.assertSingle(SpaceEvents.AcceptInvite(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on decline invite emits the expected Event`() { + fun `clicking on decline invite emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED) val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( hasMoreToLoad = false, children = listOf(aSpaceRoom), eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_decline) + clickOn(CommonStrings.action_decline) eventsRecorder.assertSingle(SpaceEvents.DeclineInvite(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on topic emits the expected Event`() { + fun `clicking on topic emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( spaceInfo = aRoomInfo(topic = A_ROOM_TOPIC), hasMoreToLoad = false, eventSink = eventsRecorder, ) ) - rule.onNodeWithText(A_ROOM_TOPIC).performClick() + onNodeWithText(A_ROOM_TOPIC).performClick() eventsRecorder.assertSingle(SpaceEvents.ShowTopicViewer(A_ROOM_TOPIC)) } @Test - fun `clicking back in manage mode emits ExitManageMode event`() { + fun `clicking back in manage mode emits ExitManageMode event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( hasMoreToLoad = false, isManageMode = true, eventSink = eventsRecorder, ) ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(SpaceEvents.ExitManageMode) } @Test - fun `clicking on room in manage mode emits ToggleRoomSelection event`() { + fun `clicking on room in manage mode emits ToggleRoomSelection event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME) val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( children = listOf(aSpaceRoom), hasMoreToLoad = false, @@ -163,14 +162,14 @@ class SpaceViewTest { eventSink = eventsRecorder, ) ) - rule.onNodeWithText(A_ROOM_NAME).performClick() + onNodeWithText(A_ROOM_NAME).performClick() eventsRecorder.assertSingle(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) } @Test - fun `clicking remove button emits RemoveSelectedRooms event`() { + fun `clicking remove button emits RemoveSelectedRooms event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( children = listOf(aSpaceRoom(roomId = A_ROOM_ID)), hasMoreToLoad = false, @@ -179,15 +178,15 @@ class SpaceViewTest { eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_remove) + clickOn(CommonStrings.action_remove) eventsRecorder.assertSingle(SpaceEvents.RemoveSelectedRooms) } @Config(qualifiers = "h1024dp") @Test - fun `clicking confirm in removal dialog emits ConfirmRoomRemoval event`() { + fun `clicking confirm in removal dialog emits ConfirmRoomRemoval event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setSpaceView( + setSpaceView( aSpaceState( children = listOf(aSpaceRoom(roomId = A_ROOM_ID)), hasMoreToLoad = false, @@ -198,14 +197,14 @@ class SpaceViewTest { ) ) // Click on the Remove button in the confirmation dialog - rule.clickOn(CommonStrings.action_remove, inDialog = true) + clickOn(CommonStrings.action_remove, inDialog = true) eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval) } @Test - fun `clicking create room button calls the expected callback`() { + fun `clicking create room button calls the expected callback`() = runAndroidComposeUiTest { val onCreateRoomClick = lambdaRecorder { } - rule.setSpaceView( + setSpaceView( aSpaceState( children = emptyList(), hasMoreToLoad = false, @@ -214,14 +213,14 @@ class SpaceViewTest { ), onCreateRoomClick = onCreateRoomClick, ) - rule.clickOn(CommonStrings.action_create_room) + clickOn(CommonStrings.action_create_room) onCreateRoomClick.assertions().isCalledOnce() } @Test - fun `clicking add existing room button calls the expected callback`() { + fun `clicking add existing room button calls the expected callback`() = runAndroidComposeUiTest { val onAddRoomClick = lambdaRecorder { } - rule.setSpaceView( + setSpaceView( aSpaceState( children = emptyList(), hasMoreToLoad = false, @@ -230,12 +229,12 @@ class SpaceViewTest { ), onAddRoomClick = onAddRoomClick, ) - rule.clickOn(CommonStrings.action_add_existing_rooms) + clickOn(CommonStrings.action_add_existing_rooms) onAddRoomClick.assertions().isCalledOnce() } } -private fun AndroidComposeTestRule.setSpaceView( +private fun AndroidComposeUiTest.setSpaceView( state: SpaceState, onBackClick: () -> Unit = EnsureNeverCalled(), onRoomClick: (SpaceRoom) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt index 92162ca82c..dd992a9d2f 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt @@ -6,56 +6,54 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.startchat.impl.joinbyaddress import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.startchat.impl.R import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class JoinBaseRoomByAddressViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `entering text emits the expected event`() { + fun `entering text emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomByAddressView( + setJoinRoomByAddressView( aJoinRoomByAddressState( eventSink = eventsRecorder, ) ) - val text = rule.activity.getString(R.string.screen_start_chat_join_room_by_address_action) - rule.onNodeWithText(text).performTextInput("#address:matrix.org") + val text = activity!!.getString(R.string.screen_start_chat_join_room_by_address_action) + onNodeWithText(text).performTextInput("#address:matrix.org") eventsRecorder.assertSingle(JoinRoomByAddressEvent.UpdateAddress("#address:matrix.org")) } @Test - fun `clicking on continue emits the expected event`() { + fun `clicking on continue emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setJoinRoomByAddressView( + setJoinRoomByAddressView( aJoinRoomByAddressState( eventSink = eventsRecorder, ) ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle(JoinRoomByAddressEvent.Continue) } } -private fun AndroidComposeTestRule.setJoinRoomByAddressView( +private fun AndroidComposeUiTest.setJoinRoomByAddressView( state: JoinRoomByAddressState, ) { setSafeContent { diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt index 9237f3433c..abcb70113b 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.startchat.impl.root import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.startchat.impl.R import io.element.android.features.startchat.impl.userlist.aRecentDirectRoomList @@ -27,70 +30,65 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class StartChatViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on back invokes the expected callback`() { + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( eventSink = eventsRecorder, ), onCloseClick = it ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on New room invokes the expected callback`() { + fun `clicking on New room invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( eventSink = eventsRecorder, ), onNewRoomClick = it ) - rule.clickOn(R.string.screen_create_room_action_create_room) + clickOn(R.string.screen_create_room_action_create_room) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on Invite people invokes the expected callback`() { + fun `clicking on Invite people invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( applicationName = "test", eventSink = eventsRecorder, ), onInviteFriendsClick = it ) - val text = rule.activity.getString(CommonStrings.action_invite_friends_to_app, "test") - rule.onNodeWithText(text).performClick() + val text = activity!!.getString(CommonStrings.action_invite_friends_to_app, "test") + onNodeWithText(text).performClick() } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on a user suggestion invokes the expected callback`() { + fun `clicking on a user suggestion invokes the expected callback`() = runAndroidComposeUiTest { val recentDirectRoomList = aRecentDirectRoomList() val firstRoom = recentDirectRoomList[0] val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(firstRoom.roomId) { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( userListState = aUserListState( recentDirectRooms = recentDirectRoomList @@ -99,42 +97,42 @@ class StartChatViewTest { ), onOpenDM = it ) - rule.onNodeWithText(firstRoom.matrixUser.getBestName()).performClick() + onNodeWithText(firstRoom.matrixUser.getBestName()).performClick() } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on Join room by address invokes the expected callback`() { + fun `clicking on Join room by address invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( eventSink = eventsRecorder, ), onJoinRoomByAddressClick = it ) - rule.clickOn(R.string.screen_start_chat_join_room_by_address_action) + clickOn(R.string.screen_start_chat_join_room_by_address_action) } } @Test - fun `clicking on room directory invokes the expected callback`() { + fun `clicking on room directory invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setStartChatView( + setStartChatView( aCreateRoomRootState( eventSink = eventsRecorder, isRoomDirectorySearchEnabled = true ), onRoomDirectorySearchClick = it ) - rule.clickOn(R.string.screen_room_directory_search_title) + clickOn(R.string.screen_room_directory_search_title) } } } -private fun AndroidComposeTestRule.setStartChatView( +private fun AndroidComposeUiTest.setStartChatView( state: StartChatState, onCloseClick: () -> Unit = EnsureNeverCalled(), onNewRoomClick: () -> Unit = EnsureNeverCalled(), diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt index 83b10e2a53..b1d81f374c 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt @@ -6,13 +6,16 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.userprofile import androidx.activity.ComponentActivity +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.userprofile.api.UserProfileEvents import io.element.android.features.userprofile.api.UserProfileState @@ -39,193 +42,188 @@ import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.ensureCalledOnceWithTwoParams import io.element.android.tests.testutils.pressBack -import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class UserProfileViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `on back button click - the expected callback is called`() = runTest { + fun `on back button click - the expected callback is called`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setUserProfileView( + setUserProfileView( goBack = callback, ) - rule.pressBack() + pressBack() } } @Test - fun `on avatar clicked - the expected callback is called`() = runTest { + fun `on avatar clicked - the expected callback is called`() = runAndroidComposeUiTest { ensureCalledOnceWithTwoParams(A_USER_NAME, AN_AVATAR_URL) { callback -> - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState(userName = A_USER_NAME, avatarUrl = AN_AVATAR_URL), openAvatarPreview = callback, ) - rule.onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() + onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() } } @Test - fun `on avatar clicked with no avatar - nothing happens`() = runTest { + fun `on avatar clicked with no avatar - nothing happens`() = runAndroidComposeUiTest { val callback = EnsureNeverCalledWithTwoParams() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState(userName = A_USER_NAME, avatarUrl = null), openAvatarPreview = callback, ) - rule.onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() + onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() } @Test - fun `on Share clicked - the expected callback is called`() = runTest { + fun `on Share clicked - the expected callback is called`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setUserProfileView( + setUserProfileView( onShareUser = callback, ) - rule.clickOn(CommonStrings.action_share) + clickOn(CommonStrings.action_share) } } @Test - fun `on Message clicked - the StartDm event is emitted`() = runTest { + fun `on Message clicked - the StartDm event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( dmRoomId = A_ROOM_ID, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_message) + clickOn(CommonStrings.action_message) eventsRecorder.assertSingle(UserProfileEvents.StartDM) } @Test - fun `on Call clicked - the expected callback is called`() = runTest { + fun `on Call clicked - the expected callback is called`() = runAndroidComposeUiTest { ensureCalledOnceWithTwoParams(A_ROOM_ID, CallIntent.AUDIO) { callback -> - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( dmRoomId = A_ROOM_ID, canCall = true, ), onStartCall = callback, ) - rule.clickOn(CommonStrings.action_call) + clickOn(CommonStrings.action_call) } } @Test - fun `on Video Call clicked - the expected callback is called`() = runTest { + fun `on Video Call clicked - the expected callback is called`() = runAndroidComposeUiTest { ensureCalledOnceWithTwoParams(A_ROOM_ID, CallIntent.VIDEO) { callback -> - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( dmRoomId = A_ROOM_ID, canCall = true, ), onStartCall = callback, ) - rule.clickOn(CommonStrings.common_video) + clickOn(CommonStrings.common_video) } } @Config(qualifiers = "h1024dp") @Test - fun `on Block user clicked - a BlockUser event is emitted with needsConfirmation`() = runTest { + fun `on Block user clicked - a BlockUser event is emitted with needsConfirmation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_dm_details_block_user) + clickOn(R.string.screen_dm_details_block_user) eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = true)) } @Test - fun `on confirming block user - a BlockUser event is emitted without needsConfirmation`() = runTest { + fun `on confirming block user - a BlockUser event is emitted without needsConfirmation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_dm_details_block_alert_action) + clickOn(R.string.screen_dm_details_block_alert_action) eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = false)) } @Test - fun `on canceling blocking a user - a ClearConfirmationDialog event is emitted`() = runTest { + fun `on canceling blocking a user - a ClearConfirmationDialog event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) } @Config(qualifiers = "h1024dp") @Test - fun `on Unblock user clicked - an UnblockUser event is emitted with needsConfirmation`() = runTest { + fun `on Unblock user clicked - an UnblockUser event is emitted with needsConfirmation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_dm_details_unblock_user) + clickOn(R.string.screen_dm_details_unblock_user) eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = true)) } @Test - fun `on confirming Unblock user - an UnblockUser event is emitted without needsConfirmation`() = runTest { + fun `on confirming Unblock user - an UnblockUser event is emitted without needsConfirmation`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, eventSink = eventsRecorder, ), ) - rule.clickOn(R.string.screen_dm_details_unblock_alert_action) + clickOn(R.string.screen_dm_details_unblock_alert_action) eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = false)) } @Test - fun `on canceling unblocking a user - a ClearConfirmationDialog event is emitted`() = runTest { + fun `on canceling unblocking a user - a ClearConfirmationDialog event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) } @Test - fun `on verify user clicked - the right callback is called`() = runTest { + fun `on verify user clicked - the right callback is called`() = runAndroidComposeUiTest { ensureCalledOnceWithParam(A_USER_ID) { callback -> - rule.setUserProfileView( + setUserProfileView( state = aUserProfileState(userId = A_USER_ID, verificationState = UserProfileVerificationState.UNVERIFIED), onVerifyClick = callback, ) - rule.clickOn(CommonStrings.common_verify_user) + clickOn(CommonStrings.common_verify_user) } } } -private fun AndroidComposeTestRule.setUserProfileView( +private fun AndroidComposeUiTest.setUserProfileView( state: UserProfileState = aUserProfileState( eventSink = EventsRecorder(expectEvents = false), ), diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt index 3219658796..3498ad7714 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt @@ -6,10 +6,12 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.userprofile.shared.blockuser -import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.userprofile.api.UserProfileEvents import io.element.android.features.userprofile.api.UserProfileState @@ -18,18 +20,15 @@ import io.element.android.features.userprofile.shared.aUserProfileState import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BlockUserDialogsTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `confirm block user emit expected Event`() { + fun `confirm block user emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { BlockUserDialogs( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, @@ -37,14 +36,14 @@ class BlockUserDialogsTest { ) ) } - rule.clickOn(R.string.screen_dm_details_block_alert_action) + clickOn(R.string.screen_dm_details_block_alert_action) eventsRecorder.assertSingle(UserProfileEvents.BlockUser(false)) } @Test - fun `cancel block user emit expected Event`() { + fun `cancel block user emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { BlockUserDialogs( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, @@ -52,14 +51,14 @@ class BlockUserDialogsTest { ) ) } - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) } @Test - fun `confirm unblock user emit expected Event`() { + fun `confirm unblock user emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { BlockUserDialogs( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, @@ -67,14 +66,14 @@ class BlockUserDialogsTest { ) ) } - rule.clickOn(R.string.screen_dm_details_unblock_alert_action) + clickOn(R.string.screen_dm_details_unblock_alert_action) eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(false)) } @Test - fun `cancel unblock user emit expected Event`() { + fun `cancel unblock user emit expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setContent { + setContent { BlockUserDialogs( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, @@ -82,7 +81,7 @@ class BlockUserDialogsTest { ) ) } - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) } } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt index 4aa63f3ab8..afab77ad76 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.verifysession.impl.incoming import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.verifysession.impl.R import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData @@ -18,59 +21,55 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class IncomingVerificationViewTest { - @get:Rule val rule = createAndroidComposeRule() - // region step Initial @Test - fun `back key pressed - ignore the verification`() { + fun `back key pressed - ignore the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @Test - fun `ignore incoming verification emits the expected event`() { + fun `ignore incoming verification emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_ignore) + clickOn(CommonStrings.action_ignore) eventsRecorder.assertSingle(IncomingVerificationViewEvents.IgnoreVerification) } @Test - fun `start incoming verification emits the expected event`() { + fun `start incoming verification emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_start_verification) + clickOn(CommonStrings.action_start_verification) eventsRecorder.assertSingle(IncomingVerificationViewEvents.StartVerification) } @Test - fun `back key pressed - when awaiting response cancels the verification`() { + fun `back key pressed - when awaiting response cancels the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial( isWaiting = true, @@ -78,16 +77,16 @@ class IncomingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } // endregion step Initial // region step Verifying @Test - fun `back key pressed - when ready to verify cancels the verification`() { + fun `back key pressed - when ready to verify cancels the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -96,14 +95,14 @@ class IncomingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @Test - fun `back key pressed - when verifying and loading emits the expected event`() { + fun `back key pressed - when verifying and loading emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -112,14 +111,14 @@ class IncomingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @Test - fun `clicking on they do not match emits the expected event`() { + fun `clicking on they do not match emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -128,14 +127,14 @@ class IncomingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_session_verification_they_dont_match) + clickOn(R.string.screen_session_verification_they_dont_match) eventsRecorder.assertSingle(IncomingVerificationViewEvents.DeclineVerification) } @Test - fun `clicking on they match emits the expected event`() { + fun `clicking on they match emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -144,35 +143,35 @@ class IncomingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_session_verification_they_match) + clickOn(R.string.screen_session_verification_they_match) eventsRecorder.assertSingle(IncomingVerificationViewEvents.ConfirmVerification) } // endregion // region step Failure @Test - fun `back key pressed - when failure resets the flow`() { + fun `back key pressed - when failure resets the flow`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Failure, eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @Test - fun `click on done - when failure resets the flow`() { + fun `click on done - when failure resets the flow`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Failure, eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_done) + clickOn(CommonStrings.action_done) eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @@ -180,33 +179,33 @@ class IncomingVerificationViewTest { // region step Completed @Test - fun `back key pressed - on Completed step emits the expected event`() { + fun `back key pressed - on Completed step emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Completed, eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } @Test - fun `when flow is completed and the user clicks on the done button, the expected event is emitted`() { + fun `when flow is completed and the user clicks on the done button, the expected event is emitted`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setIncomingVerificationView( + setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Completed, eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_done) + clickOn(CommonStrings.action_done) eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) } // endregion - private fun AndroidComposeTestRule.setIncomingVerificationView( + private fun AndroidComposeUiTest.setIncomingVerificationView( state: IncomingVerificationState, ) { setContent { diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt index 71b55fac10..1c96c5c2af 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt @@ -6,11 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.features.verifysession.impl.outgoing import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.verifysession.impl.R import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData @@ -21,58 +24,54 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class OutgoingVerificationViewTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `back key pressed - when canceled resets the flow`() { + fun `back key pressed - when canceled resets the flow`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Canceled, eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Reset) } @Test - fun `back key pressed - when awaiting response cancels the verification`() { + fun `back key pressed - when awaiting response cancels the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.AwaitingOtherDeviceResponse, eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel) } @Test - fun `back key pressed - when ready to verify cancels the verification`() { + fun `back key pressed - when ready to verify cancels the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Ready, eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel) } @Test - fun `back key pressed - when verifying and not loading declines the verification`() { + fun `back key pressed - when verifying and not loading declines the verification`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -81,14 +80,14 @@ class OutgoingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification) } @Test - fun `back key pressed - when verifying and loading does nothing`() { + fun `back key pressed - when verifying and loading does nothing`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -97,42 +96,42 @@ class OutgoingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.pressBackKey() + pressBackKey() eventsRecorder.assertEmpty() } @Test - fun `back key pressed - on Completed exits the flow`() { + fun `back key pressed - on Completed exits the flow`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> - rule.setOutgoingVerificationView( + setOutgoingVerificationView( onBack = callback, state = anOutgoingVerificationState( step = OutgoingVerificationState.Step.Completed, ), ) - rule.pressBackKey() + pressBackKey() } } @Test - fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { + fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Completed, eventSink = eventsRecorder ), onFinished = callback, ) - rule.clickOn(CommonStrings.action_continue) + clickOn(CommonStrings.action_continue) } } @Test - fun `clicking on they match emits the expected event`() { + fun `clicking on they match emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -141,14 +140,14 @@ class OutgoingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_session_verification_they_match) + clickOn(R.string.screen_session_verification_they_match) eventsRecorder.assertSingle(OutgoingVerificationViewEvents.ConfirmVerification) } @Test - fun `clicking on they do not match emits the expected event`() { + fun `clicking on they do not match emits the expected event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setOutgoingVerificationView( + setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( data = aEmojisSessionVerificationData(), @@ -157,11 +156,11 @@ class OutgoingVerificationViewTest { eventSink = eventsRecorder ), ) - rule.clickOn(R.string.screen_session_verification_they_dont_match) + clickOn(R.string.screen_session_verification_they_dont_match) eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification) } - private fun AndroidComposeTestRule.setOutgoingVerificationView( + private fun AndroidComposeUiTest.setOutgoingVerificationView( state: OutgoingVerificationState, onLearnMoreClick: () -> Unit = EnsureNeverCalled(), onFinished: () -> Unit = EnsureNeverCalled(), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 919d8fbcc7..3a06ccd49c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ camera = "1.6.0" work = "2.11.2" # Compose -compose_bom = "2026.03.01" +compose_bom = "2026.04.01" # Coroutines coroutines = "1.10.2" diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt index 4cbb35a85e..3f4c3aa6c3 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.mediaviewer.impl.details import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.ui.strings.CommonStrings @@ -21,43 +24,38 @@ import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class MediaDeleteConfirmationBottomSheetTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on Cancel invokes expected callback`() { + fun `clicking on Cancel invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDeleteConfirmation() ensureCalledOnce { callback -> - rule.setMediaDeleteConfirmationBottomSheet( + setMediaDeleteConfirmationBottomSheet( state = state, onDismiss = callback, ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) } } @Test - fun `clicking on Remove invokes expected callback`() { + fun `clicking on Remove invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDeleteConfirmation() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDeleteConfirmationBottomSheet( + setMediaDeleteConfirmationBottomSheet( state = state, onDelete = callback, ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertExists() - rule.clickOn(CommonStrings.action_remove) + onNodeWithText(activity!!.getString(CommonStrings.action_remove)).assertExists() + clickOn(CommonStrings.action_remove) } } } -private fun AndroidComposeTestRule.setMediaDeleteConfirmationBottomSheet( +private fun AndroidComposeUiTest.setMediaDeleteConfirmationBottomSheet( state: MediaBottomSheetState.DeleteConfirmation, onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(), onDismiss: () -> Unit = EnsureNeverCalled(), diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt index 21a06f9568..5b0f105aea 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.mediaviewer.impl.details import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.ui.strings.CommonStrings @@ -20,97 +23,92 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.setSafeContent -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class MediaDetailsBottomSheetTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test @Config(qualifiers = "h1024dp") - fun `clicking on View in timeline invokes expected callback`() { + fun `clicking on View in timeline invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, onViewInTimeline = callback, ) - rule.clickOn(CommonStrings.action_view_in_timeline) + clickOn(CommonStrings.action_view_in_timeline) } } @Test @Config(qualifiers = "h1024dp") - fun `clicking on Share invokes expected callback`() { + fun `clicking on Share invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, onShare = callback, ) - rule.clickOn(CommonStrings.action_share) + clickOn(CommonStrings.action_share) } } @Test @Config(qualifiers = "h1024dp") - fun `clicking on Forward invokes expected callback`() { + fun `clicking on Forward invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, onForward = callback, ) - rule.clickOn(CommonStrings.action_forward) + clickOn(CommonStrings.action_forward) } } @Test @Config(qualifiers = "h1024dp") - fun `clicking on Download invokes expected callback`() { + fun `clicking on Download invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, onDownload = callback, ) - rule.clickOn(CommonStrings.action_download) + clickOn(CommonStrings.action_download) } } @Config(qualifiers = "h1024dp") @Test - fun `clicking on Delete invokes expected callback`() { + fun `clicking on Delete invokes expected callback`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails() ensureCalledOnceWithParam(state.eventId) { callback -> - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, onDelete = callback, ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_delete)).assertExists() - rule.clickOn(CommonStrings.action_delete) + onNodeWithText(activity!!.getString(CommonStrings.action_delete)).assertExists() + clickOn(CommonStrings.action_delete) } } @Config(qualifiers = "h1024dp") @Test - fun `Remove is not present if canDelete is false`() { + fun `Remove is not present if canDelete is false`() = runAndroidComposeUiTest { val state = aMediaBottomSheetStateDetails( canDelete = false, ) - rule.setMediaDetailsBottomSheet( + setMediaDetailsBottomSheet( state = state, ) - rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.action_remove)).assertDoesNotExist() } } -private fun AndroidComposeTestRule.setMediaDetailsBottomSheet( +private fun AndroidComposeUiTest.setMediaDetailsBottomSheet( state: MediaBottomSheetState.Details, onViewInTimeline: (EventId) -> Unit = EnsureNeverCalledWithParam(), onShare: (EventId) -> Unit = EnsureNeverCalledWithParam(), diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt index 9eded788aa..fdd447c4a6 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt @@ -6,18 +6,21 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.mediaviewer.impl.viewer import android.net.Uri import androidx.activity.ComponentActivity import androidx.annotation.StringRes +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeDown +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.mediaviewer.impl.details.aMediaBottomSheetStateDetails @@ -30,30 +33,26 @@ import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.setSafeContent import io.mockk.mockk -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class MediaViewerViewTest { - @get:Rule val rule = createAndroidComposeRule() - private val mockMediaUrl: Uri = mockk("localMediaUri") @Test - fun `clicking on back invokes expected callback`() { + fun `clicking on back invokes expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMediaViewerState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMediaViewerView( + setMediaViewerView( state = state, onBackClick = callback, ) - rule.pressBack() + pressBack() } eventsRecorder.assertList( listOf( @@ -103,16 +102,16 @@ class MediaViewerViewTest { data: MediaViewerPageData.MediaViewerData, @StringRes contentDescriptionRes: Int, expectedEvent: MediaViewerEvent, - ) { + ) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setMediaViewerView( + setMediaViewerView( aMediaViewerState( listData = listOf(data), eventSink = eventsRecorder ), ) - val contentDescription = rule.activity.getString(contentDescriptionRes) - rule.onNodeWithContentDescription(contentDescription).performClick() + val contentDescription = activity!!.getString(contentDescriptionRes) + onNodeWithContentDescription(contentDescription).performClick() eventsRecorder.assertList( listOf( MediaViewerEvent.OnNavigateTo(0), @@ -159,16 +158,16 @@ class MediaViewerViewTest { data: MediaViewerPageData.MediaViewerData, @StringRes textRes: Int, expectedEvent: MediaViewerEvent, - ) { + ) = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setMediaViewerView( + setMediaViewerView( aMediaViewerState( listData = listOf(data), mediaBottomSheetState = aMediaBottomSheetStateDetails(), eventSink = eventsRecorder ), ) - rule.clickOn(textRes) + clickOn(textRes) eventsRecorder.assertList( listOf( MediaViewerEvent.OnNavigateTo(0), @@ -179,24 +178,25 @@ class MediaViewerViewTest { } @Test - fun `clicking on image hides the overlay`() { + fun `clicking on image hides the overlay`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMediaViewerState( eventSink = eventsRecorder ) - rule.setMediaViewerView( + setMediaViewerView( state = state, ) // Ensure that the action are visible - val contentDescription = rule.activity.getString(CommonStrings.action_share) - rule.onNodeWithContentDescription(contentDescription) + val resources = activity!!.resources + val contentDescription = resources.getString(CommonStrings.action_share) + onNodeWithContentDescription(contentDescription) .assertExists() .assertHasClickAction() - val imageContentDescription = rule.activity.getString(CommonStrings.common_image) - rule.onNodeWithContentDescription(imageContentDescription).performClick() + val imageContentDescription = resources.getString(CommonStrings.common_image) + onNodeWithContentDescription(imageContentDescription).performClick() // Give time for the animation (? since even by removing AnimatedVisibility it still fails) - rule.mainClock.advanceTimeBy(1_000) - rule.onNodeWithContentDescription(contentDescription) + mainClock.advanceTimeBy(1_000) + onNodeWithContentDescription(contentDescription) .assertDoesNotExist() eventsRecorder.assertList( listOf( @@ -207,19 +207,19 @@ class MediaViewerViewTest { } @Test - fun `clicking swipe on the image invokes the expected callback`() { + fun `clicking swipe on the image invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val state = aMediaViewerState( eventSink = eventsRecorder ) ensureCalledOnce { callback -> - rule.setMediaViewerView( + setMediaViewerView( state = state, onBackClick = callback, ) - val imageContentDescription = rule.activity.getString(CommonStrings.common_image) - rule.onNodeWithContentDescription(imageContentDescription).performTouchInput { swipeDown(startY = centerY) } - rule.mainClock.advanceTimeBy(1_000) + val imageContentDescription = activity!!.getString(CommonStrings.common_image) + onNodeWithContentDescription(imageContentDescription).performTouchInput { swipeDown(startY = centerY) } + mainClock.advanceTimeBy(1_000) } eventsRecorder.assertList( listOf( @@ -230,18 +230,18 @@ class MediaViewerViewTest { } @Test - fun `error case, click on retry emits the expected Event`() { + fun `error case, click on retry emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val data = aMediaViewerPageData( downloadedMedia = AsyncData.Failure(IllegalStateException("error")), ) - rule.setMediaViewerView( + setMediaViewerView( aMediaViewerState( listData = listOf(data), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_retry) + clickOn(CommonStrings.action_retry) eventsRecorder.assertList( listOf( MediaViewerEvent.OnNavigateTo(0), @@ -252,18 +252,18 @@ class MediaViewerViewTest { } @Test - fun `error case, click on cancel emits the expected Event`() { + fun `error case, click on cancel emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() val data = aMediaViewerPageData( downloadedMedia = AsyncData.Failure(IllegalStateException("error")), ) - rule.setMediaViewerView( + setMediaViewerView( aMediaViewerState( listData = listOf(data), eventSink = eventsRecorder ), ) - rule.clickOn(CommonStrings.action_cancel) + clickOn(CommonStrings.action_cancel) eventsRecorder.assertList( listOf( MediaViewerEvent.OnNavigateTo(0), @@ -274,7 +274,7 @@ class MediaViewerViewTest { } } -private fun AndroidComposeTestRule.setMediaViewerView( +private fun AndroidComposeUiTest.setMediaViewerView( state: MediaViewerState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt index 9a65ca0ad5..4840569c0e 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt @@ -6,12 +6,15 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.textcomposer.impl.components.markdown import android.widget.EditText import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.core.text.getSpans import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat @@ -32,66 +35,54 @@ import io.element.android.libraries.textcomposer.model.SuggestionType import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState import io.element.android.tests.testutils.EnsureCalledOnceWithParam import io.element.android.tests.testutils.EventsRecorder -import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class MarkdownTextInputTest { - @get:Rule val rule = createAndroidComposeRule() - @Test - fun `when user types onTyping is triggered with value 'true'`() = runTest { + fun `when user types onTyping is triggered with value 'true'`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialFocus = true) val onTyping = EnsureCalledOnceWithParam(expectedParam = true, result = Unit) - rule.setMarkdownTextInput(state = state, onTyping = onTyping) - rule.activityRule.scenario.onActivity { - it.findEditor().setText("Test") - } - rule.awaitIdle() + setMarkdownTextInput(state = state, onTyping = onTyping) + activity!!.findEditor().setText("Test") + awaitIdle() onTyping.assertSuccess() } @Test - fun `when user removes text onTyping is triggered with value 'false'`() = runTest { + fun `when user removes text onTyping is triggered with value 'false'`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialFocus = true) val onTyping = EventsRecorder() - rule.setMarkdownTextInput(state = state, onTyping = onTyping) - rule.activityRule.scenario.onActivity { - val editText = it.findEditor() - editText.setText("Test") - editText.setText("") - editText.setText(null) - } - rule.awaitIdle() + setMarkdownTextInput(state = state, onTyping = onTyping) + val editText = activity!!.findEditor() + editText.setText("Test") + editText.setText("") + editText.setText(null) + awaitIdle() onTyping.assertList(listOf(true, false, false)) } @Test - fun `when user types something that's not a mention onSuggestionReceived is triggered with 'null'`() = runTest { + fun `when user types something that's not a mention onSuggestionReceived is triggered with 'null'`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialFocus = true) val onSuggestionReceived = EventsRecorder() - rule.setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived) - rule.activityRule.scenario.onActivity { - it.findEditor().setText("Test") - } - rule.awaitIdle() + setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived) + activity!!.findEditor().setText("Test") + awaitIdle() onSuggestionReceived.assertSingle(null) } @Test - fun `when user types something that's a mention onSuggestionReceived is triggered a real value`() = runTest { + fun `when user types something that's a mention onSuggestionReceived is triggered a real value`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialFocus = true) val onSuggestionReceived = EventsRecorder() - rule.setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived) - rule.activityRule.scenario.onActivity { - it.findEditor().setText("@") - it.findEditor().setText("#") - it.findEditor().setText("/") - } - rule.awaitIdle() + setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived) + val editor = activity!!.findEditor() + editor.setText("@") + editor.setText("#") + editor.setText("/") + awaitIdle() onSuggestionReceived.assertList( listOf( // User mention suggestion @@ -105,69 +96,59 @@ class MarkdownTextInputTest { } @Test - fun `when the selection changes in the UI the state is updated`() = runTest { + fun `when the selection changes in the UI the state is updated`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = true) - rule.setMarkdownTextInput(state = state) - rule.activityRule.scenario.onActivity { - val editor = it.findEditor() - editor.setSelection(2) - } - rule.awaitIdle() + setMarkdownTextInput(state = state) + val editor = activity!!.findEditor() + editor.setSelection(2) + awaitIdle() // Selection is updated assertThat(state.selection).isEqualTo(2..2) } @Test - fun `when the selection state changes in the view is updated`() = runTest { + fun `when the selection state changes in the view is updated`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = true) - rule.setMarkdownTextInput(state = state) - var editor: EditText? = null - rule.activityRule.scenario.onActivity { - editor = it.findEditor() - state.selection = 2..2 - } - rule.awaitIdle() + setMarkdownTextInput(state = state) + val editor = activity!!.findEditor() + state.selection = 2..2 + awaitIdle() // Selection state is updated - assertThat(editor?.selectionStart).isEqualTo(2) - assertThat(editor?.selectionEnd).isEqualTo(2) + assertThat(editor.selectionStart).isEqualTo(2) + assertThat(editor.selectionEnd).isEqualTo(2) } @Test - fun `when the view focus changes the state is updated`() = runTest { + fun `when the view focus changes the state is updated`() = runAndroidComposeUiTest { val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = false) - rule.setMarkdownTextInput(state = state) - rule.activityRule.scenario.onActivity { - val editor = it.findEditor() - editor.requestFocus() - } + setMarkdownTextInput(state = state) + val editor = activity!!.findEditor() + editor.requestFocus() // Focus state is updated assertThat(state.hasFocus).isTrue() } @Test - fun `inserting a mention replaces the existing text with a span`() = runTest { + fun `inserting a mention replaces the existing text with a span`() = runAndroidComposeUiTest { val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) }) val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true) state.currentSuggestion = Suggestion(0, 1, SuggestionType.Mention, "") - rule.setMarkdownTextInput(state = state) - var editor: EditText? = null - rule.activityRule.scenario.onActivity { - editor = it.findEditor() - state.insertSuggestion( - ResolvedSuggestion.Member(roomMember = aRoomMember()), - aMentionSpanProvider(permalinkParser), - ) - } - rule.awaitIdle() + setMarkdownTextInput(state = state) + val editor = activity!!.findEditor() + state.insertSuggestion( + ResolvedSuggestion.Member(roomMember = aRoomMember()), + aMentionSpanProvider(permalinkParser), + ) + awaitIdle() // Text is replaced with a placeholder - assertThat(editor?.editableText.toString()).isEqualTo("@ ") + assertThat(editor.editableText.toString()).isEqualTo("@ ") // The placeholder contains a MentionSpan - val mentionSpans = editor?.editableText?.getSpans(0, 2).orEmpty() + val mentionSpans = editor.editableText?.getSpans(0, 2).orEmpty() assertThat(mentionSpans).isNotEmpty() } - private fun AndroidComposeTestRule.setMarkdownTextInput( + private fun AndroidComposeUiTest.setMarkdownTextInput( state: MarkdownTextEditorState = aMarkdownTextEditorState(), onTyping: (Boolean) -> Unit = {}, onSuggestionReceived: (Suggestion?) -> Unit = {}, diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt index 0ba6c22710..0244673ea5 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt @@ -6,60 +6,58 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.troubleshoot.impl import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class TroubleshootNotificationsViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `press menu back invokes the expected callback`() { + fun `press menu back invokes the expected callback`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { - rule.setTroubleshootNotificationsView( + setTroubleshootNotificationsView( state = aTroubleshootNotificationsState( eventSink = eventsRecorder ), onBackClick = it, ) - rule.pressBack() + pressBack() } } @Test - fun `clicking on run test emits the expected Event`() { + fun `clicking on run test emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTroubleshootNotificationsView( + setTroubleshootNotificationsView( aTroubleshootNotificationsState( eventSink = eventsRecorder ), ) - rule.onNodeWithText("Run tests").performClick() + onNodeWithText("Run tests").performClick() eventsRecorder.assertSingle(TroubleshootNotificationsEvents.StartTests) } @Config(qualifiers = "h1024dp") @Test - fun `clicking on run test again emits the expected Event`() { + fun `clicking on run test again emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTroubleshootNotificationsView( + setTroubleshootNotificationsView( aTroubleshootNotificationsState( tests = listOf( aTroubleshootTestStateFailure( @@ -69,7 +67,7 @@ class TroubleshootNotificationsViewTest { eventSink = eventsRecorder ), ) - rule.onNodeWithText("Run tests again").performClick() + onNodeWithText("Run tests again").performClick() eventsRecorder.assertList( listOf( TroubleshootNotificationsEvents.RetryFailedTests, @@ -80,9 +78,9 @@ class TroubleshootNotificationsViewTest { @Config(qualifiers = "h1024dp") @Test - fun `clicking on quick fix emits the expected Event`() { + fun `clicking on quick fix emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setTroubleshootNotificationsView( + setTroubleshootNotificationsView( aTroubleshootNotificationsState( tests = listOf( aTroubleshootTestStateFailure( @@ -92,7 +90,7 @@ class TroubleshootNotificationsViewTest { eventSink = eventsRecorder ), ) - rule.onNodeWithText("Attempt to fix").performClick() + onNodeWithText("Attempt to fix").performClick() eventsRecorder.assertList( listOf( TroubleshootNotificationsEvents.RetryFailedTests, @@ -102,7 +100,7 @@ class TroubleshootNotificationsViewTest { } } -private fun AndroidComposeTestRule.setTroubleshootNotificationsView( +private fun AndroidComposeUiTest.setTroubleshootNotificationsView( state: TroubleshootNotificationsState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt index fa4e65ad9a..94cde37452 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt @@ -6,14 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.libraries.troubleshoot.impl.history import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_FORMATTED_DATE @@ -23,67 +26,62 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PushHistoryViewTest { - @get:Rule - val rule = createAndroidComposeRule() - @Test - fun `clicking on Reset sends a PushHistoryEvents`() { + fun `clicking on Reset sends a PushHistoryEvents`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPushHistoryView( + setPushHistoryView( aPushHistoryState( pushCounter = 123, eventSink = eventsRecorder, ), ) - val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) - rule.onNodeWithContentDescription(menuContentDescription).performClick() - rule.clickOn(CommonStrings.action_reset) + val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) + onNodeWithContentDescription(menuContentDescription).performClick() + clickOn(CommonStrings.action_reset) eventsRecorder.assertSingle(PushHistoryEvents.Reset(requiresConfirmation = true)) // Also check that the push counter is rendered - rule.onNodeWithText("123").assertExists() + onNodeWithText("123").assertExists() } @Test - fun `clicking on show only errors sends a PushHistoryEvents(true)`() { + fun `clicking on show only errors sends a PushHistoryEvents(true)`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPushHistoryView( + setPushHistoryView( aPushHistoryState( showOnlyErrors = false, eventSink = eventsRecorder, ), ) - val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) - rule.onNodeWithContentDescription(menuContentDescription).performClick() - rule.onNodeWithText("Show only errors").performClick() + val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) + onNodeWithContentDescription(menuContentDescription).performClick() + onNodeWithText("Show only errors").performClick() eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true)) } @Test - fun `clicking on show only errors sends a PushHistoryEvents(false)`() { + fun `clicking on show only errors sends a PushHistoryEvents(false)`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPushHistoryView( + setPushHistoryView( aPushHistoryState( showOnlyErrors = true, eventSink = eventsRecorder, ), ) - val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) - rule.onNodeWithContentDescription(menuContentDescription).performClick() - rule.onNodeWithText("Show only errors").performClick() + val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) + onNodeWithContentDescription(menuContentDescription).performClick() + onNodeWithText("Show only errors").performClick() eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false)) } @Test - fun `clicking on an invalid event has no effect`() { + fun `clicking on an invalid event has no effect`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder(expectEvents = false) - rule.setPushHistoryView( + setPushHistoryView( aPushHistoryState( pushHistoryItems = listOf( aPushHistoryItem( @@ -93,14 +91,14 @@ class PushHistoryViewTest { eventSink = eventsRecorder, ), ) - rule.onNodeWithText(A_FORMATTED_DATE).performClick() + onNodeWithText(A_FORMATTED_DATE).performClick() // No callback invoked } @Test - fun `clicking on a valid event emits the expected Event`() { + fun `clicking on a valid event emits the expected Event`() = runAndroidComposeUiTest { val eventsRecorder = EventsRecorder() - rule.setPushHistoryView( + setPushHistoryView( aPushHistoryState( pushHistoryItems = listOf( aPushHistoryItem( @@ -113,7 +111,7 @@ class PushHistoryViewTest { eventSink = eventsRecorder, ), ) - rule.onNodeWithText(A_FORMATTED_DATE).performClick() + onNodeWithText(A_FORMATTED_DATE).performClick() eventsRecorder.assertSingle( PushHistoryEvents.NavigateTo( sessionId = A_SESSION_ID, @@ -124,7 +122,7 @@ class PushHistoryViewTest { } } -private fun AndroidComposeTestRule.setPushHistoryView( +private fun AndroidComposeUiTest.setPushHistoryView( state: PushHistoryState, onBackClick: () -> Unit = EnsureNeverCalled(), ) { diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt index 12cfe44b44..d7ce9e2d28 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt @@ -6,15 +6,17 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.tests.testutils import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import io.element.android.libraries.designsystem.utils.LocalUiTestMode import org.junit.Assert.assertFalse -import org.junit.rules.TestRule import kotlin.coroutines.CoroutineContext object RobolectricDispatcherCleaner { @@ -52,7 +54,7 @@ object RobolectricDispatcherCleaner { } } -fun AndroidComposeTestRule.setSafeContent( +fun AndroidComposeUiTest.setSafeContent( clearAndroidUiDispatcher: Boolean = false, content: @Composable () -> Unit, ) { diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt index d78f570a31..a473e6bd22 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt @@ -6,10 +6,14 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalTestApi::class) + package io.element.android.tests.testutils import androidx.activity.ComponentActivity import androidx.annotation.StringRes +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.assertIsDisplayed @@ -19,19 +23,17 @@ import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isDialog -import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import io.element.android.libraries.ui.strings.CommonStrings -import org.junit.rules.TestRule val trueMatcher = SemanticsMatcher("true matcher") { true } -fun AndroidComposeTestRule.clickOn( +fun AndroidComposeUiTest.clickOn( @StringRes res: Int, inDialog: Boolean = false, ) { - val text = activity.getString(res) + val text = activity!!.getString(res) onNode( hasText(text) and hasClickAction() and if (inDialog) hasAnyAncestor(isDialog()) else trueMatcher ) @@ -41,28 +43,28 @@ fun AndroidComposeTestRule.clickOn( /** * Press the back button in the app bar. */ -fun AndroidComposeTestRule.pressBack() { - val text = activity.getString(CommonStrings.action_back) +fun AndroidComposeUiTest.pressBack() { + val text = activity!!.getString(CommonStrings.action_back) onNode(hasContentDescription(text)).performClick() } /** * Press the back key. */ -fun AndroidComposeTestRule.pressBackKey() { - activity.onBackPressedDispatcher.onBackPressed() +fun AndroidComposeUiTest.pressBackKey() { + activity!!.onBackPressedDispatcher.onBackPressed() } fun SemanticsNodeInteractionsProvider.pressTag(tag: String) { onNode(hasTestTag(tag)).performClick() } -fun AndroidComposeTestRule.assertNoNodeWithText(@StringRes res: Int) { - val text = activity.getString(res) +fun AndroidComposeUiTest.assertNoNodeWithText(@StringRes res: Int) { + val text = activity!!.getString(res) onNodeWithText(text).assertDoesNotExist() } -fun AndroidComposeTestRule.assertNodeWithTextIsDisplayed(@StringRes res: Int) { - val text = activity.getString(res) +fun AndroidComposeUiTest.assertNodeWithTextIsDisplayed(@StringRes res: Int) { + val text = activity!!.getString(res) onNodeWithText(text).assertIsDisplayed() }