summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/androidTest
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-03-13 13:54:12 +0100
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-03-14 14:54:25 +0100
commit9cb1e26a0ad83c363592674eefbaa462de24cd21 (patch)
tree868117761094f4bcdd06bccf0feed7c042bf825e /android/app/src/androidTest
parent9e138799b96fea7cb38f045d2667053f5e11b1d9 (diff)
downloadmullvadvpn-9cb1e26a0ad83c363592674eefbaa462de24cd21.tar.xz
mullvadvpn-9cb1e26a0ad83c363592674eefbaa462de24cd21.zip
Add screen tests for custom lists
Diffstat (limited to 'android/app/src/androidTest')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/Actions.kt15
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt51
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialogTest.kt144
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialogTest.kt76
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialogTest.kt144
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreenTest.kt246
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreenTest.kt105
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreenTest.kt170
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt226
9 files changed, 1114 insertions, 63 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/Actions.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/Actions.kt
new file mode 100644
index 0000000000..ebd570252a
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/Actions.kt
@@ -0,0 +1,15 @@
+package net.mullvad.mullvadvpn
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.invokeGlobalAssertions
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.performTouchInput
+
+fun SemanticsNodeInteraction.performLongClick(): SemanticsNodeInteraction {
+ @OptIn(ExperimentalTestApi::class) return this.invokeGlobalAssertions().performLongClickImpl()
+}
+
+private fun SemanticsNodeInteraction.performLongClickImpl(): SemanticsNodeInteraction {
+ return performTouchInput { longClick() }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt
new file mode 100644
index 0000000000..5a20438c23
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt
@@ -0,0 +1,51 @@
+package net.mullvad.mullvadvpn.compose.data
+
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.PortRange
+import net.mullvad.mullvadvpn.model.RelayEndpointData
+import net.mullvad.mullvadvpn.model.RelayList
+import net.mullvad.mullvadvpn.model.RelayListCity
+import net.mullvad.mullvadvpn.model.RelayListCountry
+import net.mullvad.mullvadvpn.model.WireguardEndpointData
+import net.mullvad.mullvadvpn.model.WireguardRelayEndpointData
+import net.mullvad.mullvadvpn.relaylist.RelayItem
+import net.mullvad.mullvadvpn.relaylist.toRelayCountries
+
+private val DUMMY_RELAY_1 =
+ net.mullvad.mullvadvpn.model.Relay(
+ hostname = "Relay host 1",
+ active = true,
+ endpointData = RelayEndpointData.Wireguard(WireguardRelayEndpointData),
+ owned = true,
+ provider = "PROVIDER"
+ )
+private val DUMMY_RELAY_2 =
+ net.mullvad.mullvadvpn.model.Relay(
+ hostname = "Relay host 2",
+ active = true,
+ endpointData = RelayEndpointData.Wireguard(WireguardRelayEndpointData),
+ owned = true,
+ provider = "PROVIDER"
+ )
+private val DUMMY_RELAY_CITY_1 = RelayListCity("Relay City 1", "RCi1", arrayListOf(DUMMY_RELAY_1))
+private val DUMMY_RELAY_CITY_2 = RelayListCity("Relay City 2", "RCi2", arrayListOf(DUMMY_RELAY_2))
+private val DUMMY_RELAY_COUNTRY_1 =
+ RelayListCountry("Relay Country 1", "RCo1", arrayListOf(DUMMY_RELAY_CITY_1))
+private val DUMMY_RELAY_COUNTRY_2 =
+ RelayListCountry("Relay Country 2", "RCo2", arrayListOf(DUMMY_RELAY_CITY_2))
+
+private val DUMMY_WIREGUARD_PORT_RANGES = ArrayList<PortRange>()
+private val DUMMY_WIREGUARD_ENDPOINT_DATA = WireguardEndpointData(DUMMY_WIREGUARD_PORT_RANGES)
+
+val DUMMY_RELAY_COUNTRIES =
+ RelayList(
+ arrayListOf(DUMMY_RELAY_COUNTRY_1, DUMMY_RELAY_COUNTRY_2),
+ DUMMY_WIREGUARD_ENDPOINT_DATA,
+ )
+ .toRelayCountries(ownership = Constraint.Any(), providers = Constraint.Any())
+
+val DUMMY_CUSTOM_LISTS =
+ listOf(
+ RelayItem.CustomList("First list", false, "1", locations = DUMMY_RELAY_COUNTRIES),
+ RelayItem.CustomList("Empty list", expanded = false, "2", locations = emptyList())
+ )
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialogTest.kt
new file mode 100644
index 0000000000..baeb5902d7
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialogTest.kt
@@ -0,0 +1,144 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+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 io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.CreateCustomListUiState
+import net.mullvad.mullvadvpn.compose.test.CREATE_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG
+import net.mullvad.mullvadvpn.model.CustomListsError
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+class CreateCustomListDialogTest {
+ @OptIn(ExperimentalTestApi::class)
+ @JvmField
+ @RegisterExtension
+ val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenNoErrorShouldShowNoErrorMessage() =
+ composeExtension.use {
+ // Arrange
+ val state = CreateCustomListUiState(error = null)
+ setContentWithTheme { CreateCustomListDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertDoesNotExist()
+ onNodeWithText(OTHER_ERROR_TEXT).assertDoesNotExist()
+ }
+
+ @Test
+ fun givenCustomListExistsShouldShowCustomListExitsErrorText() =
+ composeExtension.use {
+ // Arrange
+ val state = CreateCustomListUiState(error = CustomListsError.CustomListExists)
+ setContentWithTheme { CreateCustomListDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertExists()
+ onNodeWithText(OTHER_ERROR_TEXT).assertDoesNotExist()
+ }
+
+ @Test
+ fun givenOtherCustomListErrorShouldShowAnErrorOccurredErrorText() =
+ composeExtension.use {
+ // Arrange
+ val state = CreateCustomListUiState(error = CustomListsError.OtherError)
+ setContentWithTheme { CreateCustomListDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertDoesNotExist()
+ onNodeWithText(OTHER_ERROR_TEXT).assertExists()
+ }
+
+ @Test
+ fun whenCancelIsClickedShouldDismissDialog() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnDismiss: () -> Unit = mockk(relaxed = true)
+ val state = CreateCustomListUiState()
+ setContentWithTheme {
+ CreateCustomListDialog(state = state, onDismiss = mockedOnDismiss)
+ }
+
+ // Act
+ onNodeWithText(CANCEL_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedOnDismiss.invoke() }
+ }
+
+ @Test
+ fun givenEmptyTextInputWhenSubmitIsClickedThenShouldNotCallOnCreate() =
+ composeExtension.use {
+ // Arrange
+ val mockedCreateCustomList: (String) -> Unit = mockk(relaxed = true)
+ val state = CreateCustomListUiState()
+ setContentWithTheme {
+ CreateCustomListDialog(state = state, createCustomList = mockedCreateCustomList)
+ }
+
+ // Act
+ onNodeWithText(CREATE_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify(exactly = 0) { mockedCreateCustomList.invoke(any()) }
+ }
+
+ @Test
+ fun givenValidTextInputWhenSubmitIsClickedThenShouldCallOnCreate() =
+ composeExtension.use {
+ // Arrange
+ val mockedCreateCustomList: (String) -> Unit = mockk(relaxed = true)
+ val inputText = "NEW LIST"
+ val state = CreateCustomListUiState()
+ setContentWithTheme {
+ CreateCustomListDialog(state = state, createCustomList = mockedCreateCustomList)
+ }
+
+ // Act
+ onNodeWithTag(CREATE_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG).performTextInput(inputText)
+ onNodeWithText(CREATE_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedCreateCustomList.invoke(inputText) }
+ }
+
+ @Test
+ fun whenInputIsChangedShouldCallOnInputChanged() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnInputChanged: () -> Unit = mockk(relaxed = true)
+ val inputText = "NEW LIST"
+ val state = CreateCustomListUiState()
+ setContentWithTheme {
+ CreateCustomListDialog(state = state, onInputChanged = mockedOnInputChanged)
+ }
+
+ // Act
+ onNodeWithTag(CREATE_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG).performTextInput(inputText)
+
+ // Assert
+ verify { mockedOnInputChanged.invoke() }
+ }
+
+ companion object {
+ private const val NAME_EXIST_ERROR_TEXT = "Name is already taken."
+ private const val OTHER_ERROR_TEXT = "An error occurred."
+ private const val CANCEL_BUTTON_TEXT = "Cancel"
+ private const val CREATE_BUTTON_TEXT = "Create"
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialogTest.kt
new file mode 100644
index 0000000000..e79c5a2fe7
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialogTest.kt
@@ -0,0 +1,76 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+class DeleteCustomListConfirmationDialogTest {
+ @OptIn(ExperimentalTestApi::class)
+ @JvmField
+ @RegisterExtension
+ val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenNameShouldShowDeleteNameTitle() =
+ composeExtension.use {
+ // Arrange
+ val name = "List should be deleted"
+ setContentWithTheme { DeleteCustomListConfirmationDialog(name = name) }
+
+ // Assert
+ onNodeWithText(DELETE_TITLE.format(name)).assertExists()
+ }
+
+ @Test
+ fun whenDeleteIsClickedShouldCallOnDelete() =
+ composeExtension.use {
+ // Arrange
+ val name = "List should be deleted"
+ val mockedOnDelete: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ DeleteCustomListConfirmationDialog(name = name, onDelete = mockedOnDelete)
+ }
+
+ // Act
+ onNodeWithText(DELETE_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedOnDelete.invoke() }
+ }
+
+ @Test
+ fun whenCancelIsClickedShouldCallOnBack() =
+ composeExtension.use {
+ // Arrange
+ val name = "List should be deleted"
+ val mockedOnBack: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ DeleteCustomListConfirmationDialog(name = name, onBack = mockedOnBack)
+ }
+
+ // Act
+ onNodeWithText(CANCEL_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedOnBack.invoke() }
+ }
+
+ companion object {
+ private const val DELETE_TITLE = "Delete \"%s\"?"
+ private const val CANCEL_BUTTON_TEXT = "Cancel"
+ private const val DELETE_BUTTON_TEXT = "Delete"
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialogTest.kt
new file mode 100644
index 0000000000..cbd6ae09d7
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialogTest.kt
@@ -0,0 +1,144 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+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 io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.UpdateCustomListUiState
+import net.mullvad.mullvadvpn.compose.test.EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG
+import net.mullvad.mullvadvpn.model.CustomListsError
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+class EditCustomListNameDialogTest {
+ @OptIn(ExperimentalTestApi::class)
+ @JvmField
+ @RegisterExtension
+ val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenNoErrorShouldShowNoErrorMessage() =
+ composeExtension.use {
+ // Arrange
+ val state = UpdateCustomListUiState(error = null)
+ setContentWithTheme { EditCustomListNameDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertDoesNotExist()
+ onNodeWithText(OTHER_ERROR_TEXT).assertDoesNotExist()
+ }
+
+ @Test
+ fun givenCustomListExistsShouldShowCustomListExitsErrorText() =
+ composeExtension.use {
+ // Arrange
+ val state = UpdateCustomListUiState(error = CustomListsError.CustomListExists)
+ setContentWithTheme { EditCustomListNameDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertExists()
+ onNodeWithText(OTHER_ERROR_TEXT).assertDoesNotExist()
+ }
+
+ @Test
+ fun givenOtherCustomListErrorShouldShowAnErrorOccurredErrorText() =
+ composeExtension.use {
+ // Arrange
+ val state = UpdateCustomListUiState(error = CustomListsError.OtherError)
+ setContentWithTheme { EditCustomListNameDialog(state = state) }
+
+ // Assert
+ onNodeWithText(NAME_EXIST_ERROR_TEXT).assertDoesNotExist()
+ onNodeWithText(OTHER_ERROR_TEXT).assertExists()
+ }
+
+ @Test
+ fun whenCancelIsClickedShouldDismissDialog() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnDismiss: () -> Unit = mockk(relaxed = true)
+ val state = UpdateCustomListUiState()
+ setContentWithTheme {
+ EditCustomListNameDialog(state = state, onDismiss = mockedOnDismiss)
+ }
+
+ // Act
+ onNodeWithText(CANCEL_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedOnDismiss.invoke() }
+ }
+
+ @Test
+ fun givenEmptyTextInputWhenSaveIsClickedThenShouldNotCallUpdateName() =
+ composeExtension.use {
+ // Arrange
+ val mockedUpdateName: (String) -> Unit = mockk(relaxed = true)
+ val state = UpdateCustomListUiState()
+ setContentWithTheme {
+ EditCustomListNameDialog(state = state, updateName = mockedUpdateName)
+ }
+
+ // Act
+ onNodeWithText(SAVE_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify(exactly = 0) { mockedUpdateName.invoke(any()) }
+ }
+
+ @Test
+ fun givenValidTextInputWhenSaveIsClickedThenShouldCallUpdateName() =
+ composeExtension.use {
+ // Arrange
+ val mockedUpdateName: (String) -> Unit = mockk(relaxed = true)
+ val inputText = "NEW NAME"
+ val state = UpdateCustomListUiState()
+ setContentWithTheme {
+ EditCustomListNameDialog(state = state, updateName = mockedUpdateName)
+ }
+
+ // Act
+ onNodeWithTag(EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG).performTextInput(inputText)
+ onNodeWithText(SAVE_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedUpdateName.invoke(inputText) }
+ }
+
+ @Test
+ fun whenInputIsChangedShouldCallOnInputChanged() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnInputChanged: () -> Unit = mockk(relaxed = true)
+ val inputText = "NEW NAME"
+ val state = UpdateCustomListUiState()
+ setContentWithTheme {
+ EditCustomListNameDialog(state = state, onInputChanged = mockedOnInputChanged)
+ }
+
+ // Act
+ onNodeWithTag(EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG).performTextInput(inputText)
+
+ // Assert
+ verify { mockedOnInputChanged.invoke() }
+ }
+
+ companion object {
+ private const val NAME_EXIST_ERROR_TEXT = "Name is already taken."
+ private const val OTHER_ERROR_TEXT = "An error occurred."
+ private const val CANCEL_BUTTON_TEXT = "Cancel"
+ private const val SAVE_BUTTON_TEXT = "Save"
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreenTest.kt
new file mode 100644
index 0000000000..5951550550
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreenTest.kt
@@ -0,0 +1,246 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+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 io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.DUMMY_RELAY_COUNTRIES
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.CustomListLocationsUiState
+import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
+import net.mullvad.mullvadvpn.compose.test.SAVE_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.relaylist.RelayItem
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class CustomListLocationsScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenLoadingStateShouldShowLoadingSpinner() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state = CustomListLocationsUiState.Loading(newList = false)
+ )
+ }
+
+ // Assert
+ onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists()
+ }
+
+ @Test
+ fun givenNewListTrueShouldShowAddLocations() =
+ composeExtension.use {
+ // Arrange
+ val newList = true
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state = CustomListLocationsUiState.Loading(newList = newList)
+ )
+ }
+
+ // Assert
+ onNodeWithText(ADD_LOCATIONS_TEXT).assertExists()
+ }
+
+ @Test
+ fun givenNewListFalseShouldShowEditLocations() =
+ composeExtension.use {
+ // Arrange
+ val newList = false
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state = CustomListLocationsUiState.Loading(newList = newList)
+ )
+ }
+
+ // Assert
+ onNodeWithText(EDIT_LOCATIONS_TEXT).assertExists()
+ }
+
+ @Test
+ fun givenListOfAvailableLocationsShouldShowThem() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Data(
+ availableLocations = DUMMY_RELAY_COUNTRIES,
+ selectedLocations = emptySet(),
+ searchTerm = ""
+ ),
+ )
+ }
+
+ // Assert
+ onNodeWithText("Relay Country 1").assertExists()
+ onNodeWithText("Relay City 1").assertDoesNotExist()
+ onNodeWithText("Relay host 1").assertDoesNotExist()
+ onNodeWithText("Relay Country 2").assertExists()
+ onNodeWithText("Relay City 2").assertDoesNotExist()
+ onNodeWithText("Relay host 2").assertDoesNotExist()
+ }
+
+ @Test
+ fun whenClickingOnRelayShouldCallOnSelectForThatRelay() =
+ composeExtension.use {
+ // Arrange
+ val selectedCountry = DUMMY_RELAY_COUNTRIES[0]
+ val mockedOnRelaySelectionClicked: (RelayItem, Boolean) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Data(
+ newList = false,
+ availableLocations = DUMMY_RELAY_COUNTRIES,
+ selectedLocations = setOf(selectedCountry)
+ ),
+ onRelaySelectionClick = mockedOnRelaySelectionClicked
+ )
+ }
+
+ // Act
+ onNodeWithText(selectedCountry.name).performClick()
+
+ // Assert
+ verify { mockedOnRelaySelectionClicked(selectedCountry, false) }
+ }
+
+ @Test
+ fun whenSearchInputIsUpdatedShouldCallOnSearchTermInput() =
+ composeExtension.use {
+ // Arrange
+ val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Data(
+ newList = false,
+ availableLocations = DUMMY_RELAY_COUNTRIES,
+ ),
+ onSearchTermInput = mockedSearchTermInput
+ )
+ }
+ val mockSearchString = "SEARCH"
+
+ // Act
+ onNodeWithText(SEARCH_PLACEHOLDER).performTextInput(mockSearchString)
+
+ // Assert
+ verify { mockedSearchTermInput.invoke(mockSearchString) }
+ }
+
+ @Test
+ fun whenSearchResultNotFoundShouldShowSearchNotFoundText() =
+ composeExtension.use {
+ // Arrange
+ val mockedSearchTermInput: (String) -> Unit = mockk(relaxed = true)
+ val mockSearchString = "SEARCH"
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Empty(
+ newList = false,
+ searchTerm = mockSearchString
+ ),
+ onSearchTermInput = mockedSearchTermInput
+ )
+ }
+
+ // Assert
+ onNodeWithText(EMPTY_SEARCH_FIRST_ROW.format(mockSearchString), substring = true)
+ .assertExists()
+ onNodeWithText(EMPTY_SEARCH_SECOND_ROW, substring = true).assertExists()
+ }
+
+ @Test
+ fun whenRelayListIsEmptyShouldShowNoRelaysText() =
+ composeExtension.use {
+ // Arrange
+ val emptySearchString = ""
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Empty(
+ newList = false,
+ searchTerm = emptySearchString
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText(NO_LOCATIONS_FOUND_TEXT).assertExists()
+ }
+
+ @Test
+ fun givenSaveIsEnabledWhenSaveClickedShouldCallOnSaveClick() =
+ composeExtension.use {
+ // Arrange
+ val mockOnSaveClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Data(
+ newList = false,
+ availableLocations = DUMMY_RELAY_COUNTRIES,
+ saveEnabled = true,
+ ),
+ onSaveClick = mockOnSaveClick
+ )
+ }
+
+ // Act
+ onNodeWithTag(SAVE_BUTTON_TEST_TAG).performClick()
+
+ // Assert
+ verify { mockOnSaveClick() }
+ }
+
+ @Test
+ fun givenSaveIsDisabledWhenSaveClickedShouldNotCallOnSaveClick() =
+ composeExtension.use {
+ // Arrange
+ val mockOnSaveClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListLocationsScreen(
+ state =
+ CustomListLocationsUiState.Content.Data(
+ newList = false,
+ availableLocations = DUMMY_RELAY_COUNTRIES,
+ saveEnabled = false,
+ ),
+ onSaveClick = mockOnSaveClick
+ )
+ }
+
+ // Act
+ onNodeWithTag(SAVE_BUTTON_TEST_TAG).performClick()
+
+ // Assert
+ verify(exactly = 0) { mockOnSaveClick() }
+ }
+
+ companion object {
+ const val ADD_LOCATIONS_TEXT = "Add locations"
+ const val EDIT_LOCATIONS_TEXT = "Edit locations"
+ const val SEARCH_PLACEHOLDER = "Search for..."
+ const val EMPTY_SEARCH_FIRST_ROW = "No result for %s."
+ const val EMPTY_SEARCH_SECOND_ROW = "Try a different search"
+ const val NO_LOCATIONS_FOUND_TEXT = "No locations found"
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreenTest.kt
new file mode 100644
index 0000000000..da9ed60997
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreenTest.kt
@@ -0,0 +1,105 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.material3.SnackbarHostState
+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 io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.DUMMY_CUSTOM_LISTS
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.CustomListsUiState
+import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
+import net.mullvad.mullvadvpn.compose.test.NEW_LIST_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.relaylist.RelayItem
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class CustomListsScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenLoadingStateShouldShowLoadingSpinner() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ CustomListsScreen(
+ state = CustomListsUiState.Loading,
+ snackbarHostState = SnackbarHostState()
+ )
+ }
+
+ // Assert
+ onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists()
+ }
+
+ @Test
+ fun givenCustomListsShouldShowTheirNames() =
+ composeExtension.use {
+ // Arrange
+ val customLists = DUMMY_CUSTOM_LISTS
+ setContentWithTheme {
+ CustomListsScreen(
+ state = CustomListsUiState.Content(customLists = customLists),
+ snackbarHostState = SnackbarHostState()
+ )
+ }
+
+ // Assert
+ onNodeWithText(customLists[0].name).assertExists()
+ onNodeWithText(customLists[1].name).assertExists()
+ }
+
+ @Test
+ fun whenNewListButtonIsClickedShouldCallAddCustomList() =
+ composeExtension.use {
+ // Arrange
+ val customLists = DUMMY_CUSTOM_LISTS
+ val mockedAddCustomList: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListsScreen(
+ state = CustomListsUiState.Content(customLists = customLists),
+ snackbarHostState = SnackbarHostState(),
+ addCustomList = mockedAddCustomList
+ )
+ }
+
+ // Act
+ onNodeWithTag(NEW_LIST_BUTTON_TEST_TAG).performClick()
+
+ // Assert
+ verify { mockedAddCustomList() }
+ }
+
+ @Test
+ fun whenACustomListIsClickedShouldCallOpenCustomList() =
+ composeExtension.use {
+ // Arrange
+ val customLists = DUMMY_CUSTOM_LISTS
+ val clickedList = DUMMY_CUSTOM_LISTS[0]
+ val mockedOpenCustomList: (RelayItem.CustomList) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ CustomListsScreen(
+ state = CustomListsUiState.Content(customLists = customLists),
+ snackbarHostState = SnackbarHostState(),
+ openCustomList = mockedOpenCustomList
+ )
+ }
+
+ // Act
+ onNodeWithText(clickedList.name).performClick()
+
+ // Assert
+ verify { mockedOpenCustomList(clickedList) }
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreenTest.kt
new file mode 100644
index 0000000000..f44441b536
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreenTest.kt
@@ -0,0 +1,170 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+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 io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.DUMMY_CUSTOM_LISTS
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.EditCustomListState
+import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
+import net.mullvad.mullvadvpn.compose.test.DELETE_DROPDOWN_MENU_ITEM_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.TOP_BAR_DROPDOWN_BUTTON_TEST_TAG
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class EditCustomListScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @BeforeEach
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun givenLoadingStateShouldShowLoadingSpinner() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme { EditCustomListScreen(state = EditCustomListState.Loading) }
+
+ // Assert
+ onNodeWithTag(CIRCULAR_PROGRESS_INDICATOR).assertExists()
+ }
+
+ @Test
+ fun givenNotFoundStateShouldShowNotFound() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme { EditCustomListScreen(state = EditCustomListState.NotFound) }
+
+ // Assert
+ onNodeWithText(NOT_FOUND_TEXT).assertExists()
+ }
+
+ @Test
+ fun givenContentStateShouldShowNameFromState() =
+ composeExtension.use {
+ // Arrange
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ setContentWithTheme {
+ EditCustomListScreen(
+ state =
+ EditCustomListState.Content(
+ id = customList.id,
+ name = customList.name,
+ locations = customList.locations
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText(customList.name)
+ }
+
+ @Test
+ fun givenContentStateShouldShowNumberOfLocationsFromState() =
+ composeExtension.use {
+ // Arrange
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ setContentWithTheme {
+ EditCustomListScreen(
+ state =
+ EditCustomListState.Content(
+ id = customList.id,
+ name = customList.name,
+ locations = customList.locations
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText(LOCATIONS_TEXT.format(customList.locations.size))
+ }
+
+ @Test
+ fun whenClickingOnDeleteDropdownShouldCallOnDeleteList() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnDelete: (String) -> Unit = mockk(relaxed = true)
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ setContentWithTheme {
+ EditCustomListScreen(
+ state =
+ EditCustomListState.Content(
+ id = customList.id,
+ name = customList.name,
+ locations = customList.locations
+ ),
+ onDeleteList = mockedOnDelete
+ )
+ }
+
+ // Act
+ onNodeWithTag(TOP_BAR_DROPDOWN_BUTTON_TEST_TAG).performClick()
+ onNodeWithTag(DELETE_DROPDOWN_MENU_ITEM_TEST_TAG).performClick()
+
+ // Assert
+ verify { mockedOnDelete(customList.name) }
+ }
+
+ @Test
+ fun whenClickingOnNameCellShouldCallOnNameClicked() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnNameClicked: (String, String) -> Unit = mockk(relaxed = true)
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ setContentWithTheme {
+ EditCustomListScreen(
+ state =
+ EditCustomListState.Content(
+ id = customList.id,
+ name = customList.name,
+ locations = customList.locations
+ ),
+ onNameClicked = mockedOnNameClicked
+ )
+ }
+
+ // Act
+ onNodeWithText(customList.name).performClick()
+
+ // Assert
+ verify { mockedOnNameClicked(customList.id, customList.name) }
+ }
+
+ @Test
+ fun whenClickingOnLocationCellShouldCallOnLocationsClicked() =
+ composeExtension.use {
+ // Arrange
+ val mockedOnLocationsClicked: (String) -> Unit = mockk(relaxed = true)
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ setContentWithTheme {
+ EditCustomListScreen(
+ state =
+ EditCustomListState.Content(
+ id = customList.id,
+ name = customList.name,
+ locations = customList.locations
+ ),
+ onLocationsClicked = mockedOnLocationsClicked
+ )
+ }
+
+ // Act
+ onNodeWithText(LOCATIONS_TEXT.format(customList.locations.size)).performClick()
+
+ // Assert
+ verify { mockedOnLocationsClicked(customList.id) }
+ }
+
+ companion object {
+ const val NOT_FOUND_TEXT = "Not found"
+ const val LOCATIONS_TEXT = "%d locations"
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
index fe28357048..28651c3852 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
@@ -3,24 +3,22 @@ package net.mullvad.mullvadvpn.compose.screen
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 io.mockk.MockKAnnotations
import io.mockk.mockk
import io.mockk.verify
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.DUMMY_CUSTOM_LISTS
+import net.mullvad.mullvadvpn.compose.data.DUMMY_RELAY_COUNTRIES
import net.mullvad.mullvadvpn.compose.setContentWithTheme
-import net.mullvad.mullvadvpn.compose.state.RelayListState
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.PortRange
-import net.mullvad.mullvadvpn.model.RelayEndpointData
-import net.mullvad.mullvadvpn.model.RelayList
-import net.mullvad.mullvadvpn.model.RelayListCity
-import net.mullvad.mullvadvpn.model.RelayListCountry
-import net.mullvad.mullvadvpn.model.WireguardEndpointData
-import net.mullvad.mullvadvpn.model.WireguardRelayEndpointData
-import net.mullvad.mullvadvpn.relaylist.toRelayCountries
+import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_CUSTOM_LIST_BOTTOM_SHEET_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_CUSTOM_LIST_HEADER_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_LOCATION_BOTTOM_SHEET_TEST_TAG
+import net.mullvad.mullvadvpn.performLongClick
+import net.mullvad.mullvadvpn.relaylist.RelayItem
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
@@ -55,12 +53,11 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
state =
- SelectLocationUiState.Data(
- relayListState =
- RelayListState.RelayList(
- countries = DUMMY_RELAY_COUNTRIES,
- selectedItem = null
- ),
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = DUMMY_RELAY_COUNTRIES,
+ selectedItem = null,
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
@@ -95,12 +92,11 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
state =
- SelectLocationUiState.Data(
- relayListState =
- RelayListState.RelayList(
- countries = updatedDummyList,
- selectedItem = updatedDummyList[0].cities[0].relays[0]
- ),
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = updatedDummyList,
+ selectedItem = updatedDummyList[0].cities[0].relays[0],
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
@@ -125,12 +121,11 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
state =
- SelectLocationUiState.Data(
- relayListState =
- RelayListState.RelayList(
- countries = emptyList(),
- selectedItem = null
- ),
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = emptyList(),
+ selectedItem = null,
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
@@ -156,8 +151,11 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
state =
- SelectLocationUiState.Data(
- relayListState = RelayListState.Empty,
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = emptyList(),
+ selectedItem = null,
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = mockSearchString
@@ -171,41 +169,143 @@ class SelectLocationScreenTest {
onNodeWithText("Try a different search", substring = true).assertExists()
}
- companion object {
- private val DUMMY_RELAY_1 =
- net.mullvad.mullvadvpn.model.Relay(
- hostname = "Relay host 1",
- active = true,
- endpointData = RelayEndpointData.Wireguard(WireguardRelayEndpointData),
- owned = true,
- provider = "PROVIDER"
- )
- private val DUMMY_RELAY_2 =
- net.mullvad.mullvadvpn.model.Relay(
- hostname = "Relay host 2",
- active = true,
- endpointData = RelayEndpointData.Wireguard(WireguardRelayEndpointData),
- owned = true,
- provider = "PROVIDER"
- )
- private val DUMMY_RELAY_CITY_1 =
- RelayListCity("Relay City 1", "RCi1", arrayListOf(DUMMY_RELAY_1))
- private val DUMMY_RELAY_CITY_2 =
- RelayListCity("Relay City 2", "RCi2", arrayListOf(DUMMY_RELAY_2))
- private val DUMMY_RELAY_COUNTRY_1 =
- RelayListCountry("Relay Country 1", "RCo1", arrayListOf(DUMMY_RELAY_CITY_1))
- private val DUMMY_RELAY_COUNTRY_2 =
- RelayListCountry("Relay Country 2", "RCo2", arrayListOf(DUMMY_RELAY_CITY_2))
+ @Test
+ fun givenNoCustomListsAndSearchIsTermIsEmptyShouldShowCustomListsEmptyText() =
+ composeExtension.use {
+ // Arrange
+ val mockSearchString = ""
+ setContentWithTheme {
+ SelectLocationScreen(
+ state =
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = emptyList(),
+ selectedItem = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = mockSearchString
+ ),
+ )
+ }
+
+ // Assert
+ onNodeWithText(CUSTOM_LISTS_EMPTY_TEXT).assertExists()
+ }
+
+ @Test
+ fun givenNoCustomListsAndSearchIsActiveShouldNotShowCustomListHeader() =
+ composeExtension.use {
+ // Arrange
+ val mockSearchString = "SEARCH"
+ setContentWithTheme {
+ SelectLocationScreen(
+ state =
+ SelectLocationUiState.Content(
+ customLists = DUMMY_CUSTOM_LISTS,
+ filteredCustomLists = emptyList(),
+ countries = emptyList(),
+ selectedItem = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = mockSearchString
+ ),
+ )
+ }
+
+ // Assert
+ onNodeWithText(CUSTOM_LISTS_EMPTY_TEXT).assertDoesNotExist()
+ onNodeWithTag(SELECT_LOCATION_CUSTOM_LIST_HEADER_TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun whenCustomListIsClickedShouldCallOnSelectRelay() =
+ composeExtension.use {
+ // Arrange
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ val mockedOnSelectRelay: (RelayItem) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ SelectLocationScreen(
+ state =
+ SelectLocationUiState.Content(
+ customLists = DUMMY_CUSTOM_LISTS,
+ filteredCustomLists = DUMMY_CUSTOM_LISTS,
+ countries = emptyList(),
+ selectedItem = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
+ ),
+ onSelectRelay = mockedOnSelectRelay
+ )
+ }
- private val DUMMY_WIREGUARD_PORT_RANGES = ArrayList<PortRange>()
- private val DUMMY_WIREGUARD_ENDPOINT_DATA =
- WireguardEndpointData(DUMMY_WIREGUARD_PORT_RANGES)
+ // Act
+ onNodeWithText(customList.name).performClick()
- private val DUMMY_RELAY_COUNTRIES =
- RelayList(
- arrayListOf(DUMMY_RELAY_COUNTRY_1, DUMMY_RELAY_COUNTRY_2),
- DUMMY_WIREGUARD_ENDPOINT_DATA,
+ // Assert
+ verify { mockedOnSelectRelay(customList) }
+ }
+
+ @Test
+ fun whenCustomListIsLongClickedShouldShowBottomSheet() =
+ composeExtension.use {
+ // Arrange
+ val customList = DUMMY_CUSTOM_LISTS[0]
+ val mockedOnSelectRelay: (RelayItem) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ SelectLocationScreen(
+ state =
+ SelectLocationUiState.Content(
+ customLists = DUMMY_CUSTOM_LISTS,
+ filteredCustomLists = DUMMY_CUSTOM_LISTS,
+ countries = emptyList(),
+ selectedItem = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
+ ),
+ onSelectRelay = mockedOnSelectRelay
)
- .toRelayCountries(ownership = Constraint.Any(), providers = Constraint.Any())
+ }
+
+ // Act
+ onNodeWithText(customList.name).performLongClick()
+
+ // Assert
+ onNodeWithTag(SELECT_LOCATION_CUSTOM_LIST_BOTTOM_SHEET_TEST_TAG)
+ }
+
+ @Test
+ fun whenLocationIsLongClickedShouldShowBottomSheet() =
+ composeExtension.use {
+ // Arrange
+ val relayItem = DUMMY_RELAY_COUNTRIES[0]
+ val mockedOnSelectRelay: (RelayItem) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ SelectLocationScreen(
+ state =
+ SelectLocationUiState.Content(
+ customLists = emptyList(),
+ filteredCustomLists = emptyList(),
+ countries = DUMMY_RELAY_COUNTRIES,
+ selectedItem = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
+ ),
+ onSelectRelay = mockedOnSelectRelay
+ )
+ }
+
+ // Act
+ onNodeWithText(relayItem.name).performLongClick()
+
+ // Assert
+ onNodeWithTag(SELECT_LOCATION_LOCATION_BOTTOM_SHEET_TEST_TAG)
+ }
+
+ companion object {
+ private const val CUSTOM_LISTS_EMPTY_TEXT = "To create a custom list press the \"︙\""
}
}