Skip to content

Commit

Permalink
Merge branch 'migrate-to-compose-navigation-droid-173'
Browse files Browse the repository at this point in the history
  • Loading branch information
albin-mullvad committed Dec 14, 2023
2 parents 3fc2cb8 + 22bd84b commit d337e05
Show file tree
Hide file tree
Showing 159 changed files with 4,509 additions and 4,602 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Line wrap the file at 100 chars. Th
#### Linux
- Prevent fragmentation when multihop is enabled by setting a default route MTU.

### Changed
#### Android
- Migrated to Compose Navigation
- Allow for full rotation
- Improve animations between screens


## [android/2023.10-beta1] - 2023-12-11
### Fixed
Expand Down
8 changes: 6 additions & 2 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ plugins {
id(Dependencies.Plugin.playPublisherId)
id(Dependencies.Plugin.kotlinAndroidId)
id(Dependencies.Plugin.kotlinParcelizeId)
id(Dependencies.Plugin.ksp) version Versions.Plugin.ksp
}

val repoRootPath = rootProject.projectDir.absoluteFile.parentFile.absolutePath
val extraAssetsDirectory = "${project.buildDir}/extraAssets"
val defaultChangeLogAssetsDirectory = "$repoRootPath/android/src/main/play/release-notes/"
val defaultChangelogAssetsDirectory = "$repoRootPath/android/src/main/play/release-notes/"
val extraJniDirectory = "${project.buildDir}/extraJni"

val credentialsPath = "${rootProject.projectDir}/credentials"
Expand Down Expand Up @@ -111,7 +112,7 @@ android {
getByName("main") {
val changelogDir =
gradleLocalProperties(rootProject.projectDir)
.getOrDefault("OVERRIDE_CHANGELOG_DIR", defaultChangeLogAssetsDirectory)
.getOrDefault("OVERRIDE_CHANGELOG_DIR", defaultChangelogAssetsDirectory)

assets.srcDirs(extraAssetsDirectory, changelogDir)
jniLibs.srcDirs(extraJniDirectory)
Expand Down Expand Up @@ -337,6 +338,9 @@ dependencies {
implementation(Dependencies.Compose.uiController)
implementation(Dependencies.Compose.ui)
implementation(Dependencies.Compose.uiUtil)
implementation(Dependencies.Compose.destinations)
ksp(Dependencies.Compose.destinationsKsp)

implementation(Dependencies.jodaTime)
implementation(Dependencies.Koin.core)
implementation(Dependencies.Koin.android)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.mullvad.mullvadvpn.compose.dialog

import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
import io.mockk.MockKAnnotations
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.test.CUSTOM_PORT_DIALOG_INPUT_TEST_TAG
import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.onNodeWithTagAndText
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class CustomPortDialogTest {
@get:Rule val composeTestRule = createComposeRule()

@Before
fun setup() {
MockKAnnotations.init(this)
}

@SuppressLint("ComposableNaming")
@Composable
private fun testWireguardCustomPortDialog(
initialPort: Int? = null,
allowedPortRanges: List<PortRange> = emptyList(),
onSave: (Int?) -> Unit = { _ -> },
onDismiss: () -> Unit = {},
) {

WireguardCustomPortDialog(
initialPort = initialPort,
allowedPortRanges = allowedPortRanges,
onSave = onSave,
onDismiss = onDismiss
)
}

@Test
fun testShowWireguardCustomPortDialogInvalidInt() {
// Input a number to make sure that a too long number does not show and it does not crash
// the app

// Arrange
composeTestRule.setContentWithTheme { testWireguardCustomPortDialog() }

// Act
composeTestRule
.onNodeWithTag(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG)
.performTextInput(invalidCustomPort)

// Assert
composeTestRule
.onNodeWithTagAndText(CUSTOM_PORT_DIALOG_INPUT_TEST_TAG, invalidCustomPort)
.assertDoesNotExist()
}

companion object {
const val invalidCustomPort = "21474836471"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package net.mullvad.mullvadvpn.compose.dialog

import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.viewmodel.DnsDialogViewState
import org.junit.Rule
import org.junit.Test

class DnsDialogTest {
@get:Rule val composeTestRule = createComposeRule()

private val defaultState =
DnsDialogViewState(
ipAddress = "",
validationResult = DnsDialogViewState.ValidationResult.Success,
isLocal = false,
isAllowLanEnabled = false,
isNewEntry = true
)

@SuppressLint("ComposableNaming")
@Composable
private fun testDnsDialog(
state: DnsDialogViewState = defaultState,
onDnsInputChange: (String) -> Unit = { _ -> },
onSaveDnsClick: () -> Unit = {},
onRemoveDnsClick: () -> Unit = {},
onDismiss: () -> Unit = {}
) {
DnsDialog(state, onDnsInputChange, onSaveDnsClick, onRemoveDnsClick, onDismiss)
}

@Test
fun testDnsDialogLanWarningShownWhenLanTrafficDisabledAndLocalAddressUsed() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = true))
}

// Assert
composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertExists()
}

@Test
fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressUsed() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = true))
}

// Assert
composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist()
}

@Test
fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndNonLocalAddressUsed() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = false))
}

// Assert
composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist()
}

@Test
fun testDnsDialogLanWarningNotShownWhenLanTrafficDisabledAndNonLocalAddressUsed() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = false))
}

// Assert
composeTestRule.onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist()
}

@Test
fun testDnsDialogSubmitButtonDisabledOnInvalidDnsAddress() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(
defaultState.copy(
ipAddress = invalidIpAddress,
validationResult = DnsDialogViewState.ValidationResult.InvalidAddress,
)
)
}

// Assert
composeTestRule.onNodeWithText("Submit").assertIsNotEnabled()
}

@Test
fun testDnsDialogSubmitButtonDisabledOnDuplicateDnsAddress() {
// Arrange
composeTestRule.setContentWithTheme {
testDnsDialog(
defaultState.copy(
ipAddress = "192.168.0.1",
validationResult = DnsDialogViewState.ValidationResult.DuplicateAddress,
)
)
}

// Assert
composeTestRule.onNodeWithText("Submit").assertIsNotEnabled()
}

companion object {
private const val LOCAL_DNS_SERVER_WARNING =
"The local DNS server will not work unless you enable " +
"\"Local Network Sharing\" under Preferences."

private const val invalidIpAddress = "300.300.300.300"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package net.mullvad.mullvadvpn.compose.dialog

import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
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.setContentWithTheme
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class MtuDialogTest {
@get:Rule val composeTestRule = createComposeRule()

@Before
fun setup() {
MockKAnnotations.init(this)
}

@SuppressLint("ComposableNaming")
@Composable
private fun testMtuDialog(
mtuInitial: Int? = null,
onSaveMtu: (Int) -> Unit = { _ -> },
onResetMtu: () -> Unit = {},
onDismiss: () -> Unit = {},
) {
MtuDialog(
mtuInitial = mtuInitial,
onSaveMtu = onSaveMtu,
onResetMtu = onResetMtu,
onDismiss = onDismiss
)
}

@Test
fun testMtuDialogWithDefaultValue() {
// Arrange
composeTestRule.setContentWithTheme { testMtuDialog() }

// Assert
composeTestRule.onNodeWithText(EMPTY_STRING).assertExists()
}

@Test
fun testMtuDialogWithEditValue() {
// Arrange
composeTestRule.setContentWithTheme {
testMtuDialog(
mtuInitial = VALID_DUMMY_MTU_VALUE,
)
}

// Assert
composeTestRule.onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists()
}

@Test
fun testMtuDialogTextInput() {
// Arrange
composeTestRule.setContentWithTheme {
testMtuDialog(
null,
)
}

// Act
composeTestRule
.onNodeWithText(EMPTY_STRING)
.performTextInput(VALID_DUMMY_MTU_VALUE.toString())

// Assert
composeTestRule.onNodeWithText(VALID_DUMMY_MTU_VALUE.toString()).assertExists()
}

@Test
fun testMtuDialogSubmitOfValidValue() {
// Arrange
val mockedSubmitHandler: (Int) -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
testMtuDialog(
VALID_DUMMY_MTU_VALUE,
onSaveMtu = mockedSubmitHandler,
)
}

// Act
composeTestRule.onNodeWithText("Submit").assertIsEnabled().performClick()

// Assert
verify { mockedSubmitHandler.invoke(VALID_DUMMY_MTU_VALUE) }
}

@Test
fun testMtuDialogSubmitButtonDisabledWhenInvalidInput() {
// Arrange
composeTestRule.setContentWithTheme {
testMtuDialog(
INVALID_DUMMY_MTU_VALUE,
)
}

// Assert
composeTestRule.onNodeWithText("Submit").assertIsNotEnabled()
}

@Test
fun testMtuDialogResetClick() {
// Arrange
val mockedClickHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
testMtuDialog(
onResetMtu = mockedClickHandler,
)
}

// Act
composeTestRule.onNodeWithText("Reset to default").performClick()

// Assert
verify { mockedClickHandler.invoke() }
}

@Test
fun testMtuDialogCancelClick() {
// Arrange
val mockedClickHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
testMtuDialog(
onDismiss = mockedClickHandler,
)
}

// Assert
composeTestRule.onNodeWithText("Cancel").performClick()

// Assert
verify { mockedClickHandler.invoke() }
}

companion object {
private const val EMPTY_STRING = ""
private const val VALID_DUMMY_MTU_VALUE = 1337
private const val INVALID_DUMMY_MTU_VALUE = 1111
}
}
Loading

0 comments on commit d337e05

Please sign in to comment.