From a4c0b3de5584e3374a72882fc07509e62d7b5d07 Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Tue, 11 Jun 2024 10:52:52 +0200 Subject: [PATCH] Add api access method details ui tests --- .../compose/data/DummyApiAccessMethods.kt | 17 ++ .../ApiAccessMethodDetailsScreenTest.kt | 258 ++++++++++++++++++ .../compose/cell/NavigationComposeCell.kt | 6 +- .../screen/ApiAccessMethodDetailsScreen.kt | 16 +- .../screen/EditApiAccessMethodScreen.kt | 7 +- .../compose/test/ComposeTestTagConstants.kt | 12 +- 6 files changed, 304 insertions(+), 12 deletions(-) create mode 100644 android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt index d1f68b468312..4c3a26835f03 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt @@ -4,8 +4,11 @@ import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodType +import net.mullvad.mullvadvpn.lib.model.Cipher +import net.mullvad.mullvadvpn.lib.model.Port private const val UUID1 = "12345678-1234-5678-1234-567812345678" +private const val UUID2 = "12345678-1234-5678-1234-567812345679" val DIRECT_ACCESS_METHOD = ApiAccessMethod( @@ -14,3 +17,17 @@ val DIRECT_ACCESS_METHOD = enabled = true, apiAccessMethodType = ApiAccessMethodType.Direct ) + +val CUSTOM_ACCESS_METHOD = + ApiAccessMethod( + id = ApiAccessMethodId.fromString(UUID2), + name = ApiAccessMethodName.fromString("ShadowSocks"), + enabled = true, + apiAccessMethodType = + ApiAccessMethodType.CustomProxy.Shadowsocks( + ip = "1.1.1.1", + port = Port(123), + password = "Password", + cipher = Cipher.RC4 + ) + ) diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt new file mode 100644 index 000000000000..b09794ac2f23 --- /dev/null +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt @@ -0,0 +1,258 @@ +package net.mullvad.mullvadvpn.compose.screen + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import io.mockk.mockk +import io.mockk.verify +import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension +import net.mullvad.mullvadvpn.compose.data.CUSTOM_ACCESS_METHOD +import net.mullvad.mullvadvpn.compose.data.DIRECT_ACCESS_METHOD +import net.mullvad.mullvadvpn.compose.setContentWithTheme +import net.mullvad.mullvadvpn.compose.state.ApiAccessMethodDetailsUiState +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_EDIT_BUTTON +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_TEST_METHOD_BUTTON +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_USE_METHOD_BUTTON +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +@OptIn(ExperimentalTestApi::class) +class ApiAccessMethodDetailsScreenTest { + @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension() + + @Test + fun whenApiAccessMethodIsNotEditableShouldNotShowDeleteAndEdit() = + composeExtension.use { + // Arrange + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = true, + isCurrentMethod = true, + testApiAccessMethodState = null + ) + ) + } + + // Assert + onNodeWithTag(API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG).assertDoesNotExist() + onNodeWithTag(API_ACCESS_DETAILS_EDIT_BUTTON).assertDoesNotExist() + } + + @Test + fun whenApiAccessMethodIsNotDisableableShouldNotBeAbleDisable() = + composeExtension.use { + // Arrange + val onEnableClicked: (Boolean) -> Unit = mockk(relaxed = true) + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = false, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onEnableClicked = onEnableClicked + ) + } + + // Act + onNodeWithText("Enable method").performClick() + + // Assert + onNodeWithText("At least one method needs to be enabled") + verify(exactly = 0) { onEnableClicked(any()) } + } + + @Test + fun whenApiAccessMethodIsCurrentMethodShouldNotBeAbleToSetCurrent() = + composeExtension.use { + // Arrange + val onUseMethodClicked: () -> Unit = mockk(relaxed = true) + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = false, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onUseMethodClicked = onUseMethodClicked + ) + } + + // Act + onNodeWithTag(API_ACCESS_USE_METHOD_BUTTON).performClick() + + // Assert + onNodeWithTag(API_ACCESS_USE_METHOD_BUTTON).assertIsNotEnabled() + onNodeWithText("This is already set as current") + verify(exactly = 0) { onUseMethodClicked() } + } + + @Test + fun whenClickingOnDeleteMethodShouldCallOnDeleteApiAccessMethodClicked() = + composeExtension.use { + // Arrange + val onDeleteApiAccessMethodClicked: (ApiAccessMethodId) -> Unit = mockk(relaxed = true) + val apiAccessMethod = CUSTOM_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = true, + isDisableable = false, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onDeleteApiAccessMethodClicked = onDeleteApiAccessMethodClicked + ) + } + + // Act + onNodeWithTag(API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG).performClick() + onNodeWithText("Delete method").performClick() + + // Assert + verify(exactly = 1) { onDeleteApiAccessMethodClicked(apiAccessMethod.id) } + } + + @Test + fun whenClickingOnEditMethodShouldCallOnEditMethodClicked() = + composeExtension.use { + // Arrange + val onEditMethodClicked: () -> Unit = mockk(relaxed = true) + val apiAccessMethod = CUSTOM_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = true, + isDisableable = false, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onEditMethodClicked = onEditMethodClicked + ) + } + + // Act + onNodeWithTag(API_ACCESS_DETAILS_EDIT_BUTTON).performClick() + + // Assert + verify(exactly = 1) { onEditMethodClicked() } + } + + @Test + fun whenClickingOnEnableMethodShouldCallOnEnableClicked() = + composeExtension.use { + // Arrange + val onEnableClicked: (Boolean) -> Unit = mockk(relaxed = true) + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = true, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onEnableClicked = onEnableClicked + ) + } + + // Act + onNodeWithText("Enable method").performClick() + + // Assert + verify(exactly = 1) { onEnableClicked(false) } + } + + @Test + fun whenClickingOnTestMethodShouldCallOnTestMethodClicked() = + composeExtension.use { + // Arrange + val onTestMethodClicked: () -> Unit = mockk(relaxed = true) + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = true, + isCurrentMethod = true, + testApiAccessMethodState = null + ), + onTestMethodClicked = onTestMethodClicked + ) + } + + // Act + onNodeWithTag(API_ACCESS_TEST_METHOD_BUTTON).performClick() + + // Assert + verify(exactly = 1) { onTestMethodClicked() } + } + + @Test + fun whenClickingOnUseMethodShouldCallOnUseMethodClicked() = + composeExtension.use { + // Arrange + val onUseMethodClicked: () -> Unit = mockk(relaxed = true) + val apiAccessMethod = DIRECT_ACCESS_METHOD + setContentWithTheme { + ApiAccessMethodDetailsScreen( + state = + ApiAccessMethodDetailsUiState.Content( + apiAccessMethodId = apiAccessMethod.id, + name = apiAccessMethod.name, + enabled = apiAccessMethod.enabled, + isEditable = false, + isDisableable = true, + isCurrentMethod = false, + testApiAccessMethodState = null + ), + onUseMethodClicked = onUseMethodClicked + ) + } + + // Act + onNodeWithTag(API_ACCESS_USE_METHOD_BUTTON).performClick() + + // Assert + verify(exactly = 1) { onUseMethodClicked() } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt index 7fbc4bdda380..fdc01ab62d59 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt @@ -67,7 +67,8 @@ fun NavigationComposeCell( showWarning: Boolean = false, bodyView: @Composable () -> Unit = { DefaultNavigationView(chevronContentDescription = title) }, isRowEnabled: Boolean = true, - onClick: () -> Unit + onClick: () -> Unit, + testTag: String = "" ) { BaseCell( onCellClicked = onClick, @@ -79,7 +80,8 @@ fun NavigationComposeCell( ) }, bodyView = { bodyView() }, - isRowEnabled = isRowEnabled + isRowEnabled = isRowEnabled, + testTag = testTag ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreen.kt index ecc67e46eeee..d1d0d4c21624 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreen.kt @@ -45,8 +45,11 @@ import net.mullvad.mullvadvpn.compose.destinations.DeleteApiAccessMethodConfirma import net.mullvad.mullvadvpn.compose.destinations.EditApiAccessMethodDestination import net.mullvad.mullvadvpn.compose.preview.ApiAccessMethodDetailsUiStatePreviewParameterProvider import net.mullvad.mullvadvpn.compose.state.ApiAccessMethodDetailsUiState +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_EDIT_BUTTON +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_TEST_METHOD_BUTTON +import net.mullvad.mullvadvpn.compose.test.API_ACCESS_USE_METHOD_BUTTON 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 net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect import net.mullvad.mullvadvpn.compose.util.OnNavResultValue @@ -176,7 +179,8 @@ private fun Content( if (state.isEditable) { NavigationComposeCell( title = stringResource(id = R.string.edit_method), - onClick = onEditMethodClicked + onClick = onEditMethodClicked, + testTag = API_ACCESS_DETAILS_EDIT_BUTTON ) HorizontalDivider() } @@ -193,14 +197,16 @@ private fun Content( } Spacer(modifier = Modifier.height(Dimens.verticalSpace)) TestMethodButton( - modifier = Modifier.padding(horizontal = Dimens.sideMargin), + modifier = + Modifier.padding(horizontal = Dimens.sideMargin).testTag(API_ACCESS_TEST_METHOD_BUTTON), testMethodState = state.testApiAccessMethodState, onTestMethod = onTestMethodClicked ) Spacer(modifier = Modifier.height(Dimens.verticalSpace)) PrimaryButton( isEnabled = !state.isCurrentMethod, - modifier = Modifier.padding(horizontal = Dimens.sideMargin), + modifier = + Modifier.padding(horizontal = Dimens.sideMargin).testTag(API_ACCESS_USE_METHOD_BUTTON), onClick = onUseMethodClicked, text = stringResource(id = R.string.use_method) ) @@ -214,7 +220,7 @@ private fun Actions(onDeleteAccessMethod: () -> Unit) { var showMenu by remember { mutableStateOf(false) } IconButton( onClick = { showMenu = true }, - modifier = Modifier.testTag(TOP_BAR_DROPDOWN_BUTTON_TEST_TAG) + modifier = Modifier.testTag(API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG) ) { Icon(painter = painterResource(id = R.drawable.icon_more_vert), contentDescription = null) if (showMenu) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreen.kt index cb0d7c0ccd97..730fd84c7d6e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreen.kt @@ -506,10 +506,9 @@ private fun EnableAuthentication( painter = painterResource(id = R.drawable.icon_tick), contentDescription = null, modifier = - Modifier.padding(end = Dimens.selectableCellTextMargin) - .alpha( - if (authenticationEnabled.not()) AlphaVisible else AlphaInvisible - ) + Modifier.alpha( + if (authenticationEnabled.not()) AlphaVisible else AlphaInvisible + ) ) } ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt index 77dd61d861fe..e3373bcee183 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt @@ -90,5 +90,15 @@ const val SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG = const val SAVE_API_ACCESS_METHOD_SAVE_BUTTON_TEST_TAG = "save_api_access_method_save_button_test_tag" -// ApiAccessList +// ApiAccessListScreen const val API_ACCESS_LIST_INFO_TEST_TAG = "api_access_list_info_test_tag" + +// ApiAccessMethodDetailsScreen +const val API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG = + "api_access_details_top_bar_dropdown_button_test_tag" +const val API_ACCESS_DETAILS_EDIT_BUTTON = + "api_access_details_edit_button_test_tag" +const val API_ACCESS_USE_METHOD_BUTTON = + "api_access_details_use_method_test_tag" +const val API_ACCESS_TEST_METHOD_BUTTON = + "api_access_details_test_method_test_tag"