Первые тесты
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
package com.github.nullptroma.wallenc.ui
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.github.nullptroma.wallenc.ui.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TextSecretsScreenContentTest {
|
||||
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
private val context get() = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
@Test
|
||||
fun showsEmptyStateWhenNoSecrets() {
|
||||
composeRule.setContent {
|
||||
MaterialTheme {
|
||||
TextSecretsScreenContent(
|
||||
uiState = TextSecretsScreenState(
|
||||
isLoading = false,
|
||||
isAvailable = true,
|
||||
items = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
composeRule.onNodeWithText(context.getString(R.string.text_secret_empty_state)).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsSecretTitle() {
|
||||
val secret = TextSecretRecord(
|
||||
id = "s1",
|
||||
title = "Production DB",
|
||||
items = listOf(TextSecretEntryRecord(label = "user", value = "admin")),
|
||||
)
|
||||
composeRule.setContent {
|
||||
MaterialTheme {
|
||||
TextSecretsScreenContent(
|
||||
uiState = TextSecretsScreenState(
|
||||
isLoading = false,
|
||||
isAvailable = true,
|
||||
items = listOf(secret),
|
||||
),
|
||||
) {
|
||||
TextSecretListCard(
|
||||
secret = secret,
|
||||
onClick = {},
|
||||
enabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
composeRule.onNodeWithText("Production DB").assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TwoFaTokensScreenContentTest {
|
||||
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
private val context get() = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
@Test
|
||||
fun showsEmptyStateWhenNoTokens() {
|
||||
composeRule.setContent {
|
||||
MaterialTheme {
|
||||
TwoFaTokensScreenContent(
|
||||
uiState = TwoFaTokensScreenState(
|
||||
isLoading = false,
|
||||
isAvailable = true,
|
||||
items = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
composeRule.onNodeWithText(context.getString(R.string.two_fa_empty_state)).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsIssuerAndAccountForToken() {
|
||||
val token = TwoFaTokenRecord(
|
||||
id = "1",
|
||||
issuer = "GitHub",
|
||||
account = "user@example.com",
|
||||
secret = "SECRET",
|
||||
)
|
||||
composeRule.setContent {
|
||||
MaterialTheme {
|
||||
TwoFaTokensScreenContent(
|
||||
uiState = TwoFaTokensScreenState(
|
||||
isLoading = false,
|
||||
isAvailable = true,
|
||||
items = listOf(token),
|
||||
),
|
||||
) {
|
||||
TwoFaTokenListHeader(
|
||||
issuer = token.issuer,
|
||||
account = token.account,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
composeRule.onNodeWithText("GitHub").assertIsDisplayed()
|
||||
composeRule.onNodeWithText("user@example.com").assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,16 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.outlined.Notes
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.outlined.Notes
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -33,7 +21,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.resources.resolveText
|
||||
|
||||
@Composable
|
||||
fun TextSecretsScreen(
|
||||
@@ -60,99 +47,19 @@ fun TextSecretsScreen(
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
TextSecretsScreenContent(
|
||||
uiState = uiState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
.padding(innerPadding),
|
||||
) {
|
||||
uiState.errorNotification?.let { notification ->
|
||||
Text(
|
||||
text = notification.resolveText(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.items.isEmpty()) {
|
||||
Text(stringResource(R.string.text_secret_empty_state))
|
||||
} else {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(uiState.items) { secret ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { onOpenSecret(secret) },
|
||||
enabled = uiState.isAvailable,
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.Notes,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(22.dp)
|
||||
.padding(top = 2.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = secret.title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.text_secret_items_count, secret.items.size),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
val fieldNames = secret.items
|
||||
.map { item ->
|
||||
item.label
|
||||
?.trim()
|
||||
.takeUnless { it.isNullOrBlank() }
|
||||
?: stringResource(R.string.text_secret_item_without_label)
|
||||
}
|
||||
if (fieldNames.isNotEmpty()) {
|
||||
fieldNames.take(3).forEach { fieldName ->
|
||||
Text(
|
||||
text = "\u2022 $fieldName",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
val hiddenCount = fieldNames.size - 3
|
||||
if (hiddenCount > 0) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.text_secret_more_fields,
|
||||
hiddenCount,
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(uiState.items) { secret ->
|
||||
TextSecretListCard(
|
||||
secret = secret,
|
||||
onClick = { onOpenSecret(secret) },
|
||||
enabled = uiState.isAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.outlined.Notes
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.resources.resolveText
|
||||
|
||||
@Composable
|
||||
fun TextSecretsScreenContent(
|
||||
uiState: TextSecretsScreenState,
|
||||
modifier: Modifier = Modifier,
|
||||
secretList: @Composable () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
uiState.errorNotification?.let { notification ->
|
||||
Text(
|
||||
text = notification.resolveText(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.items.isEmpty()) {
|
||||
Text(stringResource(R.string.text_secret_empty_state))
|
||||
} else {
|
||||
secretList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextSecretListCard(
|
||||
secret: TextSecretRecord,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.Notes,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(22.dp)
|
||||
.padding(top = 2.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column(modifier = Modifier.padding(end = 12.dp)) {
|
||||
Text(
|
||||
text = secret.title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.text_secret_items_count, secret.items.size),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
val fieldNames = secret.items
|
||||
.map { item ->
|
||||
item.label
|
||||
?.trim()
|
||||
.takeUnless { it.isNullOrBlank() }
|
||||
?: stringResource(R.string.text_secret_item_without_label)
|
||||
}
|
||||
if (fieldNames.isNotEmpty()) {
|
||||
fieldNames.take(3).forEach { fieldName ->
|
||||
Text(
|
||||
text = "\u2022 $fieldName",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
val hiddenCount = fieldNames.size - 3
|
||||
if (hiddenCount > 0) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.text_secret_more_fields,
|
||||
hiddenCount,
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,30 +122,14 @@ fun TwoFaTokensScreen(
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
TwoFaTokensScreenContent(
|
||||
uiState = uiState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
.padding(innerPadding),
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator()
|
||||
return@Column
|
||||
}
|
||||
|
||||
uiState.errorNotification?.let { notification ->
|
||||
Text(
|
||||
text = notification.resolveText(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.items.isEmpty()) {
|
||||
Text(stringResource(R.string.two_fa_empty_state))
|
||||
} else {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(uiState.items) { item ->
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(uiState.items) { item ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
@@ -159,30 +143,11 @@ fun TwoFaTokensScreen(
|
||||
.padding(14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
TwoFaTokenListHeader(
|
||||
issuer = item.issuer,
|
||||
account = item.account,
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Lock,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(22.dp)
|
||||
.padding(top = 2.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = item.issuer,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
text = item.account,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = { editingToken = item },
|
||||
@@ -282,7 +247,6 @@ fun TwoFaTokensScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.resources.resolveText
|
||||
|
||||
@Composable
|
||||
fun TwoFaTokensScreenContent(
|
||||
uiState: TwoFaTokensScreenState,
|
||||
modifier: Modifier = Modifier,
|
||||
tokenList: @Composable () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator()
|
||||
return@Column
|
||||
}
|
||||
|
||||
uiState.errorNotification?.let { notification ->
|
||||
Text(
|
||||
text = notification.resolveText(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.items.isEmpty()) {
|
||||
Text(stringResource(R.string.two_fa_empty_state))
|
||||
} else {
|
||||
tokenList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TwoFaTokenListHeader(
|
||||
issuer: String,
|
||||
account: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Lock,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(22.dp)
|
||||
.padding(top = 2.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = issuer,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
Text(
|
||||
text = account,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.github.nullptroma.wallenc.ui
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.github.nullptroma.wallenc.ui.navigation
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [33])
|
||||
class WallencDeepLinksTest {
|
||||
|
||||
@Test
|
||||
fun matchesWallencViewIntent() {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(WallencDeepLinks.MAIN_URI_PATTERN))
|
||||
assertTrue(intent.matchesWallencDeepLink())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsUnrelatedIntent() {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
assertFalse(intent.matchesWallencDeepLink())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matchesTasksAndSettingsHosts() {
|
||||
assertTrue(
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(WallencDeepLinks.TASKS_URI_PATTERN))
|
||||
.matchesWallencDeepLink(),
|
||||
)
|
||||
assertTrue(
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(WallencDeepLinks.SETTINGS_URI_PATTERN))
|
||||
.matchesWallencDeepLink(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [33])
|
||||
class OtpAuthUriParserTest {
|
||||
|
||||
@Test
|
||||
fun parsesStandardTotpUri() {
|
||||
val uri = "otpauth://totp/GitHub:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&digits=6&period=30"
|
||||
val parsed = parseOtpAuthTotpUri(uri)
|
||||
assertNotNull(parsed)
|
||||
assertEquals("GitHub", parsed!!.issuer)
|
||||
assertEquals("user@example.com", parsed.account)
|
||||
assertEquals("JBSWY3DPEHPK3PXP", parsed.secret)
|
||||
assertEquals(6, parsed.digits)
|
||||
assertEquals(30, parsed.periodSeconds)
|
||||
assertEquals("SHA1", parsed.algorithm)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsNonOtpauthScheme() {
|
||||
assertNull(parseOtpAuthTotpUri("https://example.com"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsMissingSecret() {
|
||||
assertNull(parseOtpAuthTotpUri("otpauth://totp/Test:account"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.tasks
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
|
||||
import com.github.nullptroma.wallenc.task.runtime.TaskOrchestrator
|
||||
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [33])
|
||||
class TaskPipelineViewModelTest {
|
||||
|
||||
@Test
|
||||
fun startTestTaskEnqueuesWork() = runBlocking {
|
||||
val orchestrator = TaskOrchestrator(Dispatchers.Default)
|
||||
val uiStrings = mockk<UiStringResolver>()
|
||||
every { uiStrings.invoke(any<Int>(), any()) } returns "Test task"
|
||||
every { uiStrings.invoke(any<Int>()) } returns "Test"
|
||||
val viewModel = TaskPipelineViewModel(orchestrator, uiStrings)
|
||||
|
||||
viewModel.startTestTask(durationSec = 0, infinityIndeterminateProgress = false)
|
||||
|
||||
withTimeout(5_000) {
|
||||
while (true) {
|
||||
val task = orchestrator.pipelineState.value.tasks.singleOrNull()
|
||||
if (task?.state is TaskRunState.Completed) break
|
||||
delay(25)
|
||||
}
|
||||
}
|
||||
val task = orchestrator.pipelineState.value.tasks.single()
|
||||
assertTrue(task.state is TaskRunState.Completed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user