Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import to.bitkit.models.NewTransactionSheetDetails
import to.bitkit.models.NewTransactionSheetDirection
import to.bitkit.models.NewTransactionSheetType
import to.bitkit.test.annotations.ComposeUi
import to.bitkit.repositories.UsdtWalletState
import to.bitkit.ui.screens.wallets.usdt.DepositUiState
import to.bitkit.ui.screens.wallets.usdt.UsdtDepositReceiveContent
import to.bitkit.ui.screens.wallets.usdt.ReceivePage
import to.bitkit.ui.screens.wallets.usdt.UsdtReceiveContent
import to.bitkit.ui.sheets.NewTransactionSheetView
import to.bitkit.ui.theme.AppThemeSurface
Expand Down Expand Up @@ -75,7 +79,8 @@ class UsdtReceivePrivacyTest {
val polygonUri = "ethereum:0xc2132D05D31c914a87C6611C10748AEb04B58e8F@137/transfer?address=$deposit"
val cases = listOf(
ReceiveCase(wallet, "Arbitrum One", arbitrumUri),
ReceiveCase(wallet, "Arbitrum One", "$arbitrumUri&uint256=1234567", "1.234567"),
ReceiveCase(wallet, "Arbitrum One", "$arbitrumUri&uint256=3500000", "3.5", "3.50"),
ReceiveCase(wallet, "Arbitrum One", "$arbitrumUri&uint256=1", "0.000001", "0.000001"),
ReceiveCase(deposit, "Polygon", "$polygonUri&uint256=3500000", "3.5"),
ReceiveCase(wallet, "Arbitrum One", arbitrumUri),
)
Expand All @@ -94,7 +99,23 @@ class UsdtReceivePrivacyTest {
AppThemeSurface {
Column {
val receive = current.value
UsdtReceiveContent(receive.address, receive.uri, null, receive.network, {}, {}, receive.amount)
if (receive.inputAmount != null) {
UsdtDepositReceiveContent(
page = ReceivePage.ADDRESS,
onPage = {},
wallet = UsdtWalletState(address = wallet, receiveUri = arbitrumUri),
receiveError = null,
state = DepositUiState(),
amount = receive.inputAmount,
onNetwork = {},
onAmount = {},
onCreate = {},
onHistory = {},
onRefresh = {},
)
} else {
UsdtReceiveContent(receive.address, receive.uri, null, receive.network, {}, {}, receive.amount)
}
}
}
}
Expand Down Expand Up @@ -142,7 +163,13 @@ class UsdtReceivePrivacyTest {
}
}

private data class ReceiveCase(val address: String, val network: String, val uri: String, val amount: String = "")
private data class ReceiveCase(
val address: String,
val network: String,
val uri: String,
val amount: String = "",
val inputAmount: String? = null,
)

@Test
fun incomingPaymentHonorsHiddenBalance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

package to.bitkit.ui.screens.paymentrequests

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
Expand All @@ -17,9 +23,15 @@ import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.synonym.paykit.PaymentRequestLifecycleState
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import to.bitkit.models.PaykitAmount
Expand All @@ -39,15 +51,31 @@ import to.bitkit.repositories.PaykitSubscriptionRecurrence
import to.bitkit.test.annotations.ComposeUi
import to.bitkit.ui.LocalCurrencies
import to.bitkit.ui.theme.AppThemeSurface
import java.util.Locale
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

@ComposeUi
class PaymentRequestsScreenTest {
private val originalLocale = Locale.getDefault()

@get:Rule
val composeTestRule = createComposeRule()

@Before
fun setup() {
Locale.setDefault(Locale.US)
}

@After
fun teardown() {
Locale.setDefault(originalLocale)
}

@Test
fun detailsDisablePayWhilePreparationIsPending() {
val isPreparing = mutableStateOf(false)
Expand Down Expand Up @@ -300,6 +328,158 @@ class PaymentRequestsScreenTest {
composeTestRule.onNodeWithTag("PaymentRequestCreate").assertDoesNotExist()
}

@Test
fun longReceiptStatusKeepsContactAndExactAmountReadable() {
val status = "Płatność zweryfikowana · 0.001647 USDT"
val asset = mutableStateOf(PaykitAsset.USDT)
val fontScale = mutableStateOf(1.3f)
composeTestRule.setContent {
PaymentRequestsTestSurface {
val density = LocalDensity.current
CompositionLocalProvider(LocalDensity provides Density(density.density, fontScale.value)) {
Box(Modifier.width(320.dp).padding(horizontal = 16.dp)) {
PaymentRequestCard(
request = request().copy(amount = PaykitAmount(asset.value, 1_647u)),
contact = PubkyProfile.forDisplay(request().counterparty, "Maria", imageUrl = null),
fiatStatus = status,
)
}
}
}
}
for (scale in listOf(1.3f, 2f)) {
composeTestRule.runOnIdle { fontScale.value = scale }
for (denomination in listOf(PaykitAsset.USDT, PaykitAsset.USD, PaykitAsset.BTC)) {
composeTestRule.runOnIdle { asset.value = denomination }
for (text in listOf("Maria", status)) {
val results = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(text).assertIsDisplayed()
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(results) }
val layout = results.single()
assertFalse(layout.hasVisualOverflow, "$text must fit without truncation")
for (word in text.split(' ')) {
val start = text.indexOf(word)
assertEquals(layout.getLineForOffset(start), layout.getLineForOffset(start + word.length - 1))
}
}
}
}
}

@Test
fun shortWordContactKeepsFullNameBesideLongReceipt() {
val name = "Ann Lee"
val status = "Payment verified · 0.001647 USDT"
val width = mutableStateOf(407.dp)
composeTestRule.setContent {
PaymentRequestsTestSurface {
Box(Modifier.width(width.value).padding(horizontal = 16.dp)) {
PaymentRequestCard(
request = request().copy(amount = PaykitAmount(PaykitAsset.USDT, 1_647u)),
contact = PubkyProfile.forDisplay(request().counterparty, name, imageUrl = null),
compactSubtitle = "09:41",
fiatStatus = status,
)
}
}
}
for (size in listOf(407.dp, 412.dp)) {
composeTestRule.runOnIdle { width.value = size }
for (text in listOf(name, status)) {
val results = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(text).assertIsDisplayed()
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(results) }
assertFalse(results.single().hasVisualOverflow, "$text must fit without truncation at $size")
}
}
}

@Test
fun shortAmountLeavesRoomForContactNameAtLargeText() {
val name = "Alexandra Payer"
composeTestRule.setContent {
PaymentRequestsTestSurface {
val density = LocalDensity.current
CompositionLocalProvider(LocalDensity provides Density(density.density, 1.3f)) {
Box(Modifier.width(375.dp).padding(horizontal = 16.dp)) {
PaymentRequestCard(
request = request().copy(amount = PaykitAmount(PaykitAsset.USD, 200u)),
contact = PubkyProfile.forDisplay(request().counterparty, name, imageUrl = null),
fiatStatus = "Pending",
)
}
}
}
}
for (text in listOf(name, "$2.00", "Pending")) {
val results = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(text).assertIsDisplayed()
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(results) }
assertTrue(results.isNotEmpty())
assertFalse(results.single().hasVisualOverflow, "$text must fit without truncation")
}
}

@Test
fun longAmountUsesAvailableCardWidthAtLargeText() {
val asset = mutableStateOf(PaykitAsset.BTC)
composeTestRule.setContent {
PaymentRequestsTestSurface {
val density = LocalDensity.current
CompositionLocalProvider(LocalDensity provides Density(density.density, 1.3f)) {
Box(Modifier.width(320.dp).padding(horizontal = 16.dp)) {
PaymentRequestCard(
request = request().copy(amount = PaykitAmount(asset.value, 1_000_000u)),
contact = PubkyProfile.forDisplay(request().counterparty, "Alice", imageUrl = null),
)
}
}
}
}
for ((value, text) in listOf(PaykitAsset.BTC to "₿ 1 000 000", PaykitAsset.USD to "$10,000.00")) {
composeTestRule.runOnIdle { asset.value = value }
val results = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(text).assertIsDisplayed()
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(results) }
assertTrue(results.isNotEmpty())
assertFalse(results.single().hasVisualOverflow, "$text must fit without truncation")
assertTrue(results.single().lineCount == 1)
}
}

@Test
fun bitcoinReceiptStatusUsesCaptionInBothUnitSettings() {
val primary = mutableStateOf(PrimaryDisplay.FIAT)
val status = "Płatność zweryfikowana · 4,12485 USDT"
composeTestRule.setContent {
PaymentRequestsTestSurface(CurrencyState(primaryDisplay = primary.value)) {
val density = LocalDensity.current
CompositionLocalProvider(LocalDensity provides Density(density.density, 1.3f)) {
Box(Modifier.width(360.dp)) {
PaymentRequestCard(
request = request().copy(amount = PaykitAmount(PaykitAsset.BTC, 5_000u)),
contact = PubkyProfile.forDisplay(request().counterparty, "Alice", imageUrl = null),
fiatStatus = status,
)
}
}
}
}
val layouts = PrimaryDisplay.entries.map { value ->
composeTestRule.runOnIdle { primary.value = value }
val results = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(status).assertIsDisplayed()
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(results) }
results.single().also { layout ->
assertFalse(layout.hasVisualOverflow)
val wordStart = status.indexOf("zweryfikowana")
val wordEnd = wordStart + "zweryfikowana".lastIndex
assertTrue(layout.getLineForOffset(wordStart) == layout.getLineForOffset(wordEnd))
}
}
assertTrue(layouts.first().layoutInput.style.fontSize == layouts.last().layoutInput.style.fontSize)
}

private fun setOutgoingRequestContent(primaryDisplay: PrimaryDisplay) {
val outgoing = request(id = "outgoing").copy(
createdAt = Clock.System.now(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package to.bitkit.ui.screens.wallets.send

import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import kotlinx.collections.immutable.persistentListOf
import org.junit.Rule
import org.junit.Test
import to.bitkit.models.PubkyProfile
import to.bitkit.test.annotations.ComposeUi
import to.bitkit.ui.theme.AppThemeSurface
import kotlin.test.assertEquals

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

@Test
fun resolvingContactShowsProgressAndPreventsAnotherSelection() {
val state = mutableStateOf(
SendContactSelectUiState(
contacts = persistentListOf(PubkyProfile.placeholder("alice"), PubkyProfile.placeholder("bob")),
resolvingPublicKey = "alice",
)
)
var selected: String? = null
composeTestRule.setContent {
AppThemeSurface {
CompositionLocalProvider(LocalInspectionMode provides true) {
SendContactSelectContent(uiState = state.value, onContactClick = { selected = it })
}
}
}
composeTestRule.onNodeWithTag("SendContactLoading_alice", useUnmergedTree = true).assertIsDisplayed()
composeTestRule.onNodeWithTag("SendContactLoading_bob", useUnmergedTree = true).assertDoesNotExist()
composeTestRule.onNodeWithTag("SendContact_alice").assertIsNotEnabled().assert(
SemanticsMatcher.expectValue(SemanticsProperties.ProgressBarRangeInfo, ProgressBarRangeInfo.Indeterminate)
)
composeTestRule.onNodeWithTag("SendContact_bob").assertIsNotEnabled().assert(
SemanticsMatcher.keyNotDefined(SemanticsProperties.ProgressBarRangeInfo)
)
composeTestRule.runOnIdle { state.value = state.value.copy(resolvingPublicKey = null) }
composeTestRule.onNodeWithTag("SendContactLoading_alice", useUnmergedTree = true).assertDoesNotExist()
composeTestRule.onNodeWithTag("SendContact_bob").assertIsEnabled().performClick()
composeTestRule.runOnIdle { assertEquals("bob", selected) }
}
}
Loading
Loading