[#329] Update wallet to use Zatoshi type (#333)

- Int64+Zcash.swift and Double+Zcash.swift removed
- Balances and amounts updated to use Zatoshi
- Remove TODOs
- Tests updated
- FIXED: Send amount being in Zatoshi is clamping $ value to 21M max -> send amount input is no longer Zatoshi typed

[329] Update wallet to use Zatoshi type (333)

- alphabetical order(s)

[329] Update wallet to use Zatoshi type (333)

- static .zero for the Zatoshi
- conformance to Equatable moved to extension

[329] Update wallet to use Zatoshi type (333)

- small improvement by reducing code duplicity
This commit is contained in:
Lukas Korba 2022-05-31 19:14:56 +02:00 committed by GitHub
parent bab1dc6f82
commit b2ae82ce1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 358 additions and 328 deletions

View File

@ -95,14 +95,12 @@
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
9E2F1C8228095AFE004E65FE /* Int64+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */; };
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C832809B606004E65FE /* DebugMenu.swift */; };
9E2F1C8C280ED6A7004E65FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */; };
9E2F1C8F280EDE09004E65FE /* Drawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8E280EDE09004E65FE /* Drawer.swift */; };
9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; };
9E391124283E4CAC0073DD9A /* ImportWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E391123283E4CAC0073DD9A /* ImportWalletTests.swift */; };
9E391129283F74590073DD9A /* Zatoshi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E391128283F74590073DD9A /* Zatoshi.swift */; };
9E39112A283F90F10073DD9A /* Double+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */; };
9E39112E283F91600073DD9A /* ZatoshiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E39112D283F91600073DD9A /* ZatoshiTests.swift */; };
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; };
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
@ -287,7 +285,6 @@
9E2DF99827CF704D00649636 /* ImportWalletStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletStore.swift; sourceTree = "<group>"; };
9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportSeedEditor.swift; sourceTree = "<group>"; };
9E2DF99B27CF704D00649636 /* ImportWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletView.swift; sourceTree = "<group>"; };
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int64+Zcash.swift"; sourceTree = "<group>"; };
9E2F1C832809B606004E65FE /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = "<group>"; };
9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
9E2F1C8E280EDE09004E65FE /* Drawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawer.swift; sourceTree = "<group>"; };
@ -330,7 +327,6 @@
9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxStore.swift; sourceTree = "<group>"; };
9EBEF87927CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidationFlowView.swift; sourceTree = "<group>"; };
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFiles.swift; sourceTree = "<group>"; };
9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Zcash.swift"; sourceTree = "<group>"; };
9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencySelectionTests.swift; sourceTree = "<group>"; };
9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAmountInputTests.swift; sourceTree = "<group>"; };
9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressInputTests.swift; sourceTree = "<group>"; };
@ -824,12 +820,10 @@
children = (
F96B41EA273B50520021B49A /* Strings.swift */,
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */,
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */,
0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */,
0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */,
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */,
9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */,
9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */,
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */,
F9C165B3274031F600592F76 /* Bindings.swift */,
@ -1286,7 +1280,6 @@
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
9E2F1C8228095AFE004E65FE /* Int64+Zcash.swift in Sources */,
9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
0DC487C32772574C00BE6A63 /* RecoveryPhraseBackupSucceededView.swift in Sources */,
@ -1310,7 +1303,6 @@
0DDB6A5127737D4A0012A410 /* RecoveryPhraseBackupFailedView.swift in Sources */,
9E391129283F74590073DD9A /* Zatoshi.swift in Sources */,
0D6D628B276A528E002FB4CC /* DropDelegate.swift in Sources */,
9E39112A283F90F10073DD9A /* Double+Zcash.swift in Sources */,
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */,
F9971A5327680DD000A2DB75 /* ProfileStore.swift in Sources */,
669FDAEB272C23C2007B9422 /* CircularFrameBadge.swift in Sources */,

View File

@ -27,9 +27,9 @@ struct HomeState: Equatable {
var sendState: SendFlowState
var scanState: ScanState
var synchronizerStatus: String
var totalBalance: Int64
var totalBalance: Zatoshi
var transactionHistoryState: TransactionHistoryFlowState
var verifiedBalance: Int64
var verifiedBalance: Zatoshi
}
// MARK: Action
@ -110,8 +110,8 @@ extension HomeReducer {
return Effect(value: .updateSynchronizerStatus)
case .updateBalance(let balance):
state.totalBalance = balance.total
state.verifiedBalance = balance.verified
state.totalBalance = Zatoshi(amount: balance.total)
state.verifiedBalance = Zatoshi(amount: balance.verified)
return .none
case .updateDrawer(let drawerOverlay):
@ -179,11 +179,11 @@ extension HomeReducer {
action: /HomeAction.send,
environment: { environment in
SendFlowEnvironment(
mnemonic: environment.mnemonic,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage,
derivationTool: environment.derivationTool,
SDKSynchronizer: environment.SDKSynchronizer
mnemonic: environment.mnemonic,
SDKSynchronizer: environment.SDKSynchronizer,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage
)
}
)
@ -282,9 +282,9 @@ extension HomeState {
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: 0,
totalBalance: Zatoshi.zero,
transactionHistoryState: .emptyPlaceHolder,
verifiedBalance: 0
verifiedBalance: Zatoshi.zero
)
}
}

View File

@ -18,7 +18,7 @@ struct HomeView: View {
Text("\(viewStore.synchronizerStatus)")
.padding(.top, 60)
Text("balance \(viewStore.totalBalance.asZecString()) ZEC")
Text("balance \(viewStore.totalBalance.decimalString()) ZEC")
.accessDebugMenuWithHiddenGesture {
viewStore.send(.debugMenuStartup)
}

View File

@ -32,11 +32,11 @@ struct SandboxView: View {
)
.debug(),
environment: SendFlowEnvironment(
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
mnemonic: .live,
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live()
)
)
)

View File

@ -23,11 +23,10 @@ struct SendFlowState: Equatable {
case done
}
var route: Route?
var isSendingTransaction = false
var memo = ""
var totalBalance: Int64 = 0
var route: Route?
var totalBalance = Zatoshi.zero
var transaction: SendFlowTransaction
var transactionAddressInputState: TransactionAddressTextFieldState
var transactionAmountInputState: TransactionAmountTextFieldState
@ -52,8 +51,8 @@ struct SendFlowState: Equatable {
transactionAmountInputState.amount > transactionAmountInputState.maxValue
}
var totalCurrencyBalance: Int64 {
(totalBalance.asHumanReadableZecBalance() * transactionAmountInputState.zecPrice).asZec()
var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: totalBalance.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
}
}
@ -67,7 +66,7 @@ enum SendFlowAction: Equatable {
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case transactionAddressInput(TransactionAddressTextFieldAction)
case transactionAmountInput(TransactionAmountTextFieldAction)
case updateBalance(Int64)
case updateBalance(Zatoshi)
case updateMemo(String)
case updateTransaction(SendFlowTransaction)
case updateRoute(SendFlowState.Route?)
@ -76,11 +75,11 @@ enum SendFlowAction: Equatable {
// MARK: - Environment
struct SendFlowEnvironment {
let derivationTool: WrappedDerivationTool
let mnemonic: WrappedMnemonic
let SDKSynchronizer: WrappedSDKSynchronizer
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
let SDKSynchronizer: WrappedSDKSynchronizer
}
// MARK: - Reducer
@ -109,7 +108,7 @@ extension SendFlowReducer {
return .none
case .updateRoute(.confirmation):
state.transaction.amount = state.transactionAmountInputState.amount
state.transaction.amount = Zatoshi(amount: state.transactionAmountInputState.amount)
state.transaction.toAddress = state.transactionAddressInputState.textFieldState.text
return .none
@ -133,7 +132,7 @@ extension SendFlowReducer {
return environment.SDKSynchronizer.sendTransaction(
with: spendingKey,
zatoshi: Int64(state.transaction.amount),
zatoshi: state.transaction.amount,
to: state.transaction.toAddress,
memo: state.transaction.memo,
from: 0
@ -172,7 +171,7 @@ extension SendFlowReducer {
case .synchronizerStateChanged(.synced):
return environment.SDKSynchronizer.getShieldedBalance()
.receive(on: environment.scheduler)
.map({ $0.total })
.map({ Zatoshi(amount: $0.total) })
.map(SendFlowAction.updateBalance)
.eraseToEffect()
@ -181,7 +180,7 @@ extension SendFlowReducer {
case .updateBalance(let balance):
state.totalBalance = balance
state.transactionAmountInputState.maxValue = balance
state.transactionAmountInputState.maxValue = balance.amount
return .none
case .updateMemo(let memo):
@ -287,7 +286,7 @@ extension SendFlowState {
.init(
route: nil,
transaction: .init(
amount: 0,
amount: Zatoshi.zero,
memo: "",
toAddress: ""
),
@ -309,11 +308,11 @@ extension SendFlowStore {
),
reducer: .default,
environment: SendFlowEnvironment(
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
mnemonic: .live,
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live()
)
)
}

View File

@ -41,11 +41,11 @@ struct SendFLowView_Previews: PreviewProvider {
),
reducer: .default,
environment: SendFlowEnvironment(
mnemonic: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
mnemonic: .live,
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live()
)
)
)

View File

@ -10,8 +10,8 @@ struct CreateTransaction: View {
return WithViewStore(store) { viewStore in
VStack {
VStack(spacing: 0) {
Text("Balance \(viewStore.totalBalance.asZecString()) ZEC")
Text("($\(viewStore.totalCurrencyBalance.asZecString()))")
Text("Balance \(viewStore.totalBalance.decimalString()) ZEC")
Text("($\(viewStore.totalCurrencyBalance.decimalString()))")
.font(.system(size: 13))
.opacity(0.6)
}
@ -32,9 +32,7 @@ struct CreateTransaction: View {
Spacer()
}
}
if viewStore.isInsufficientFunds {
} else if viewStore.isInsufficientFunds {
HStack {
Text("insufficient funds")
.foregroundColor(.red)

View File

@ -6,7 +6,7 @@ struct TransactionConfirmation: View {
var body: some View {
VStack {
Text("Send \(viewStore.transaction.amount.asZecString()) ZEC")
Text("Send \(viewStore.transaction.amount.decimalString()) ZEC")
.padding()
Text("To \(viewStore.transaction.toAddress)")

View File

@ -101,7 +101,7 @@ extension TransactionState {
id: "2",
status: .paid(success: true),
subtitle: "",
zecAmount: 25
zecAmount: Zatoshi(amount: 25)
)
}
}
@ -153,7 +153,7 @@ extension IdentifiedArrayOf where Element == TransactionState {
id: String($0),
status: .paid(success: true),
subtitle: "",
zecAmount: 25
zecAmount: Zatoshi(amount: 25)
)
}
)

View File

@ -52,7 +52,7 @@ extension TransactionHistoryFlowView {
Spacer()
Text(transaction.status == .received ? "+" : "")
+ Text("\(transaction.zecAmount.asZecString()) ZEC")
+ Text("\(transaction.zecAmount.decimalString()) ZEC")
}
}
.navigationLink(

View File

@ -9,7 +9,7 @@ import Foundation
/// Simple model that holds data throughout the `SendFlow` feature
struct SendFlowTransaction: Equatable {
var amount: Int64
var amount: Zatoshi
var memo: String
var toAddress: String
}
@ -17,7 +17,7 @@ struct SendFlowTransaction: Equatable {
extension SendFlowTransaction {
static var placeholder: Self {
.init(
amount: 0,
amount: Zatoshi.zero,
memo: "",
toAddress: ""
)

View File

@ -25,7 +25,7 @@ struct TransactionState: Equatable, Identifiable {
var id: String
var status: Status
var subtitle: String
var zecAmount: Int64
var zecAmount: Zatoshi
}
extension TransactionState {
@ -36,7 +36,7 @@ extension TransactionState {
status = sent ? .paid(success: confirmedTransaction.minedHeight > 0) : .received
subtitle = "sent"
zAddress = confirmedTransaction.toAddress
zecAmount = (sent ? -Int64(confirmedTransaction.value) : Int64(confirmedTransaction.value))
zecAmount = sent ? Zatoshi(amount: -Int64(confirmedTransaction.value)) : Zatoshi(amount: Int64(confirmedTransaction.value))
if let memo = confirmedTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
}
@ -51,7 +51,7 @@ extension TransactionState {
expirationHeight = pendingTransaction.expiryHeight
subtitle = "pending"
zAddress = pendingTransaction.toAddress
zecAmount = -Int64(pendingTransaction.value)
zecAmount = Zatoshi(amount: -Int64(pendingTransaction.value))
if let memo = pendingTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
}
@ -64,7 +64,7 @@ extension TransactionState {
extension TransactionState {
static func placeholder(
date: Date,
amount: Int64,
amount: Zatoshi,
shielded: Bool = true,
status: Status = .received,
subtitle: String = "",
@ -80,14 +80,14 @@ extension TransactionState {
id: uuid,
status: status,
subtitle: subtitle,
zecAmount: status == .received ? amount : -amount
zecAmount: status == .received ? amount : Zatoshi(amount: -amount.amount)
)
}
}
struct TransactionStateMockHelper {
var date: TimeInterval
var amount: Int64
var amount: Zatoshi
var shielded = true
var status: TransactionState.Status = .received
var subtitle = "cleared"

View File

@ -17,8 +17,8 @@ typealias TransactionAddressTextFieldReducer = Reducer<
typealias TransactionAddressTextFieldStore = Store<TransactionAddressTextFieldState, TransactionAddressTextFieldAction>
struct TransactionAddressTextFieldState: Equatable {
var textFieldState: TCATextFieldState
var isValidAddress = false
var textFieldState: TCATextFieldState
}
enum TransactionAddressTextFieldAction: Equatable {

View File

@ -59,11 +59,11 @@ struct TransactionAmountTextField_Previews: PreviewProvider {
TransactionAmountTextField(
store: TransactionAmountTextFieldStore(
initialState: .init(
currencySelectionState: .init(currencyType: .usd),
textFieldState: .init(
validationType: .floatingPoint,
text: ""
),
currencySelectionState: .init(currencyType: .usd)
)
),
reducer: .default,
environment: .init()

View File

@ -16,29 +16,12 @@ typealias TransactionAmountTextFieldReducer = Reducer<
typealias TransactionAmountTextFieldStore = Store<TransactionAmountTextFieldState, TransactionAmountTextFieldAction>
struct TransactionAmountTextFieldState: Equatable {
var textFieldState: TCATextFieldState
var amount: Int64 = 0
var currencySelectionState: CurrencySelectionState
var maxValue: Int64 = 0
var textFieldState: TCATextFieldState
// TODO: - Get the ZEC price from the SDK, issue 311, https://github.com/zcash/secant-ios-wallet/issues/311
var zecPrice = 140.0
var amount: Int64 {
switch currencySelectionState.currencyType {
case .zec:
return (textFieldState.text.doubleValue ?? 0.0).asZec()
case .usd:
return ((textFieldState.text.doubleValue ?? 0.0) / zecPrice).asZec()
}
}
var maxCurrencyConvertedValue: Int64 {
switch currencySelectionState.currencyType {
case .zec:
return maxValue
case .usd:
return (maxValue.asHumanReadableZecBalance() * zecPrice).asZec()
}
}
var zecPrice = Decimal(140.0)
var isMax: Bool {
return amount == maxValue
@ -47,9 +30,10 @@ struct TransactionAmountTextFieldState: Equatable {
enum TransactionAmountTextFieldAction: Equatable {
case clearValue
case currencySelection(CurrencySelectionAction)
case setMax
case textField(TCATextFieldAction)
case currencySelection(CurrencySelectionAction)
case updateAmount
}
struct TransactionAmountTextFieldEnvironment: Equatable {}
@ -59,43 +43,65 @@ extension TransactionAmountTextFieldReducer {
[
textFieldReducer,
currencySelectionReducer,
maxOverride,
currencyUpdate
amountTextFieldReducer
]
)
static let maxOverride = TransactionAmountTextFieldReducer { state, action, _ in
static let amountTextFieldReducer = TransactionAmountTextFieldReducer { state, action, _ in
switch action {
case .setMax:
state.textFieldState.text = "\(state.maxCurrencyConvertedValue.asZecString())"
let maxValueAsZec = Decimal(state.maxValue) / Decimal(Zatoshi.Constants.oneZecInZatoshi)
let currencyType = state.currencySelectionState.currencyType
let maxCurrencyConvertedValue: NSDecimalNumber = currencyType == .zec ?
NSDecimalNumber(decimal: maxValueAsZec).roundedZec :
NSDecimalNumber(decimal: maxValueAsZec * state.zecPrice).roundedZec
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
let decimalString = NumberFormatter.zcashNumberFormatter.string(from: maxCurrencyConvertedValue) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
case .clearValue:
state.textFieldState.text = ""
return .none
case .textField(.set(let amount)):
return Effect(value: .updateAmount)
default: break
}
return .none
}
static let currencyUpdate = TransactionAmountTextFieldReducer { state, action, _ in
switch action {
case .currencySelection:
guard let currentDoubleValue = state.textFieldState.text.doubleValue else {
case .updateAmount:
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
guard var number = NumberFormatter.zcashNumberFormatter.number(from: state.textFieldState.text) else {
state.amount = 0
return .none
}
switch state.currencySelectionState.currencyType {
case .zec:
state.amount = NSDecimalNumber(decimal: number.decimalValue * Decimal(Zatoshi.Constants.oneZecInZatoshi)).roundedZec.int64Value
case .usd:
let decimal = (number.decimalValue / state.zecPrice) * Decimal(Zatoshi.Constants.oneZecInZatoshi)
state.amount = NSDecimalNumber(decimal: decimal).roundedZec.int64Value
}
return .none
case .currencySelection:
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
guard let number = NumberFormatter.zcashNumberFormatter.number(from: state.textFieldState.text) else {
state.amount = 0
return .none
}
let currencyType = state.currencySelectionState.currencyType
let newValue = currencyType == .zec ?
currentDoubleValue / state.zecPrice :
currentDoubleValue * state.zecPrice
state.textFieldState.text = "\(newValue.asZecString())"
default: break
number.decimalValue / state.zecPrice :
number.decimalValue * state.zecPrice
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
let decimalString = NumberFormatter.zcashNumberFormatter.string(from: NSDecimalNumber(decimal: newValue)) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
}
return .none
}
private static let textFieldReducer: TransactionAmountTextFieldReducer = TCATextFieldReducer.default.pullback(
@ -113,13 +119,13 @@ extension TransactionAmountTextFieldReducer {
extension TransactionAmountTextFieldState {
static let placeholder = TransactionAmountTextFieldState(
textFieldState: .placeholder,
currencySelectionState: CurrencySelectionState()
currencySelectionState: CurrencySelectionState(),
textFieldState: .placeholder
)
static let amount = TransactionAmountTextFieldState(
textFieldState: .amount,
currencySelectionState: CurrencySelectionState()
currencySelectionState: CurrencySelectionState(),
textFieldState: .amount
)
}

View File

@ -1,19 +0,0 @@
//
// Double+Zcash.swift
// secant-testnet
//
// Created by Lukáš Korba on 06.05.2022.
//
import Foundation
// TODO: Improve with decimals and zatoshi type, issue #272 (https://github.com/zcash/secant-ios-wallet/issues/272)
extension Double {
func asZec() -> Int64 {
return Int64((self * 100_000_000).rounded())
}
func asZecString() -> String {
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self)) ?? ""
}
}

View File

@ -1,19 +0,0 @@
//
// Int64+Zcash.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.04.2022.
//
import Foundation
// TODO: Improve with decimals and zatoshi type, issue #272 (https://github.com/zcash/secant-ios-wallet/issues/272)
extension Int64 {
func asHumanReadableZecBalance() -> Double {
Double(self) / Double(100_000_000)
}
func asZecString() -> String {
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self.asHumanReadableZecBalance())) ?? ""
}
}

View File

@ -14,6 +14,8 @@ struct Zatoshi {
static let maxZatoshi: Int64 = Constants.oneZecInZatoshi * Constants.maxZecSupply
}
static var zero: Zatoshi { Zatoshi() }
static var decimalHandler = NSDecimalNumberHandler(
roundingMode: NSDecimalNumber.RoundingMode.bankers,
scale: 8,
@ -32,8 +34,8 @@ struct Zatoshi {
}
/// Converts `Zatoshi` to human readable format, up to 8 fraction digits
var decimalString: String {
decimalValue.roundedZec.stringValue
func decimalString(formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> String {
formatter.string(from: decimalValue.roundedZec) ?? ""
}
/// Converts `Decimal` to `Zatoshi`
@ -61,14 +63,20 @@ struct Zatoshi {
}
}
extension NSDecimalNumber {
/// Converts `NSDecimalNumber` to human readable format, up to 8 fraction digits
var decimalString: String {
self.rounding(accordingToBehavior: Zatoshi.decimalHandler).stringValue
extension Zatoshi: Equatable {
static func == (lhs: Zatoshi, rhs: Zatoshi) -> Bool {
lhs.amount == rhs.amount
}
}
extension NSDecimalNumber {
/// Round the decimal to 8 fraction digits
var roundedZec: NSDecimalNumber {
self.rounding(accordingToBehavior: Zatoshi.decimalHandler)
}
/// Converts `NSDecimalNumber` to human readable format, up to 8 fraction digits
var decimalString: String {
self.roundedZec.stringValue
}
}

View File

@ -57,7 +57,7 @@ protocol WrappedSDKSynchronizer {
func sendTransaction(
with spendingKey: String,
zatoshi: Int64,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
@ -220,7 +220,7 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func sendTransaction(
with spendingKey: String,
zatoshi: Int64,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
@ -229,7 +229,7 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
Future { [weak self] promise in
self?.synchronizer?.sendToAddress(
spendingKey: spendingKey,
zatoshi: zatoshi,
zatoshi: zatoshi.amount,
toAddress: recipientAddress,
memo: memo,
from: account) { result in
@ -301,11 +301,11 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getAllClearedTransactions() -> Effect<[TransactionState], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: 1, status: .paid(success: false)),
TransactionStateMockHelper(date: 1651039101, amount: 2),
TransactionStateMockHelper(date: 1651039000, amount: 3, status: .paid(success: true)),
TransactionStateMockHelper(date: 1651039505, amount: 4),
TransactionStateMockHelper(date: 1651039404, amount: 5)
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: false)),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2)),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .paid(success: true)),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4)),
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5))
]
return Effect(
@ -324,10 +324,10 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getAllPendingTransactions() -> Effect<[TransactionState], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039606, amount: 6, status: .paid(success: false), subtitle: "pending"),
TransactionStateMockHelper(date: 1651039303, amount: 7, subtitle: "pending"),
TransactionStateMockHelper(date: 1651039707, amount: 8, status: .paid(success: true), subtitle: "pending"),
TransactionStateMockHelper(date: 1651039808, amount: 9, subtitle: "pending")
TransactionStateMockHelper(date: 1651039606, amount: Zatoshi(amount: 6), status: .paid(success: false), subtitle: "pending"),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending"),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending"),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending")
]
return Effect(
@ -360,7 +360,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func sendTransaction(
with spendingKey: String,
zatoshi: Int64,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
@ -375,7 +375,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
id: "id",
status: .paid(success: true),
subtitle: "sub",
zecAmount: 10
zecAmount: Zatoshi(amount: 10)
)
return Effect(value: Result.success(transactionState))
@ -408,11 +408,11 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getAllClearedTransactions() -> Effect<[TransactionState], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: 1, status: .paid(success: false), uuid: "aa11"),
TransactionStateMockHelper(date: 1651039101, amount: 2, uuid: "bb22"),
TransactionStateMockHelper(date: 1651039000, amount: 3, status: .paid(success: true), uuid: "cc33"),
TransactionStateMockHelper(date: 1651039505, amount: 4, uuid: "dd44"),
TransactionStateMockHelper(date: 1651039404, amount: 5, uuid: "ee55")
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: false), uuid: "aa11"),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2), uuid: "bb22"),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .paid(success: true), uuid: "cc33"),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), uuid: "dd44"),
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5), uuid: "ee55")
]
return Effect(
@ -432,10 +432,16 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getAllPendingTransactions() -> Effect<[TransactionState], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039606, amount: 6, status: .paid(success: false), subtitle: "pending", uuid: "ff66"),
TransactionStateMockHelper(date: 1651039303, amount: 7, subtitle: "pending", uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: 8, status: .paid(success: true), subtitle: "pending", uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: 9, subtitle: "pending", uuid: "ii99")
TransactionStateMockHelper(
date: 1651039606,
amount: Zatoshi(amount: 6),
status: .paid(success: false),
subtitle: "pending",
uuid: "ff66"
),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending", uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending", uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending", uuid: "ii99")
]
return Effect(
@ -469,7 +475,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func sendTransaction(
with spendingKey: String,
zatoshi: Int64,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int

View File

@ -10,8 +10,6 @@ import XCTest
import ComposableArchitecture
import ZcashLightClientKit
// TODO: these tests will be updated with the Zatoshi/Balance representative once done, issue #272 https://github.com/zcash/secant-ios-wallet/issues/272
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
// swiftlint:disable type_body_length
@ -32,11 +30,11 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage),
derivationTool: .live(),
SDKSynchronizer: MockWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: MockWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage)
)
let store = TestStore(
@ -64,7 +62,7 @@ class SendTests: XCTestCase {
id: "id",
status: .paid(success: true),
subtitle: "sub",
zecAmount: 10
zecAmount: Zatoshi(amount: 10)
)
// check the success transaction to be received back
@ -88,11 +86,11 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: storage)
)
let store = TestStore(
@ -127,11 +125,11 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -169,11 +167,11 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -185,17 +183,19 @@ class SendTests: XCTestCase {
// Checks the computed property `isInvalidAmountFormat` which controls the error message to be shown on the screen
// With empty input it must be false
store.send(.transactionAmountInput(.textField(.set(""))))
store.receive(.transactionAmountInput(.updateAmount))
}
func testInvalidAddressFormatEmptyInput() throws {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -226,20 +226,20 @@ class SendTests: XCTestCase {
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
textFieldState: .amount,
currencySelectionState: CurrencySelectionState(),
maxValue: 501_300
maxValue: 501_300,
textFieldState: .amount
)
)
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -257,9 +257,21 @@ class SendTests: XCTestCase {
)
}
store.receive(.transactionAmountInput(.updateAmount)) { state in
state.transactionAmountInputState.amount = 501_299
}
store.send(.transactionAmountInput(.textField(.set("0.00501301")))) { state in
state.transactionAmountInputState.textFieldState.text = "0.00501301"
state.transactionAmountInputState.textFieldState.valid = true
XCTAssertFalse(
state.isInsufficientFunds,
"Send Tests: `testFundsSufficiency` is expected to be false but it's \(state.isInsufficientFunds)"
)
}
store.receive(.transactionAmountInput(.updateAmount)) { state in
state.transactionAmountInputState.amount = 501_301
XCTAssertTrue(
state.isInsufficientFunds,
"Send Tests: `testFundsSufficiency` is expected to be true but it's \(state.isInsufficientFunds)"
@ -273,11 +285,11 @@ class SendTests: XCTestCase {
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -286,18 +298,18 @@ class SendTests: XCTestCase {
environment: testEnvironment
)
try amountFormatTest("1.234", true, 123_400_000, store)
try amountFormatTest("1,234", true, 123_400_000_000, store)
try amountFormatTest("1 234", true, 123_400_000_000, store)
try amountFormatTest("1,234.567", true, 123_456_700_000, store)
try amountFormatTest("1.", true, 100_000_000, store)
try amountFormatTest("1..", false, 0, store)
try amountFormatTest("1,.", false, 0, store)
try amountFormatTest("1.,", false, 0, store)
try amountFormatTest("1,,", false, 0, store)
try amountFormatTest("1,23", false, 0, store)
try amountFormatTest("1 23", false, 0, store)
try amountFormatTest("1.2.3", false, 0, store)
try amountFormatTest("1.234", true, 123_400_000, false, store)
try amountFormatTest("1,234", true, 123_400_000_000, false, store)
try amountFormatTest("1 234", true, 123_400_000_000, true, store)
try amountFormatTest("1,234.567", true, 123_456_700_000, false, store)
try amountFormatTest("1.", true, 100_000_000, false, store)
try amountFormatTest("1..", false, 0, false, store)
try amountFormatTest("1,.", false, 0, true, store)
try amountFormatTest("1.,", false, 0, true, store)
try amountFormatTest("1,,", false, 0, true, store)
try amountFormatTest("1,23", false, 0, true, store)
try amountFormatTest("1 23", false, 0, true, store)
try amountFormatTest("1.2.3", false, 0, true, store)
}
func testValidForm() throws {
@ -308,24 +320,25 @@ class SendTests: XCTestCase {
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
amount: 501_301,
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.00501301"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302
)
)
)
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -353,24 +366,24 @@ class SendTests: XCTestCase {
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
currencySelectionState: CurrencySelectionState(),
maxValue: 501_300,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.00501301"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_300
)
)
)
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -398,24 +411,24 @@ class SendTests: XCTestCase {
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.00501301"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302
)
)
)
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -443,24 +456,24 @@ class SendTests: XCTestCase {
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.0.0501301"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_302
)
)
)
let testScheduler = DispatchQueue.test
let testEnvironment = SendFlowEnvironment(
mnemonic: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
derivationTool: .live(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
)
let store = TestStore(
@ -488,16 +501,20 @@ private extension SendTests {
_ amount: String,
_ expectedValidationResult: Bool,
_ expectedAmount: Int64,
_ expectedToReceive: Bool,
_ store: TestStore<SendFlowState, SendFlowState, SendFlowAction, SendFlowAction, SendFlowEnvironment>
) throws {
store.send(.transactionAmountInput(.textField(.set(amount)))) { state in
state.transactionAmountInputState.textFieldState.text = amount
state.transactionAmountInputState.textFieldState.valid = expectedValidationResult
XCTAssertEqual(
expectedAmount,
state.transactionAmountInputState.amount,
"Send Tests: `amountFormatTest` expected amount is \(expectedAmount) but result is \(state.isInvalidAddressFormat)"
)
}
if expectedToReceive {
store.receive(.transactionAmountInput(.updateAmount))
} else {
store.receive(.transactionAmountInput(.updateAmount)) { state in
state.transactionAmountInputState.amount = expectedAmount
}
}
}
}

View File

@ -9,8 +9,6 @@ import XCTest
@testable import secant_testnet
import ComposableArchitecture
// TODO: these tests will be updated with the Zatoshi/Balance representative once done, issue #272 https://github.com/zcash/secant-ios-wallet/issues/272
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
class TransactionAmountTextFieldTests: XCTestCase {
@ -20,13 +18,13 @@ class TransactionAmountTextFieldTests: XCTestCase {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
currencySelectionState: CurrencySelectionState(),
maxValue: 501_301,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.002"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_301
)
),
reducer: TransactionAmountTextFieldReducer.default,
environment: TransactionAmountTextFieldEnvironment()
@ -34,7 +32,10 @@ class TransactionAmountTextFieldTests: XCTestCase {
store.send(.setMax) { state in
state.textFieldState.text = "0.00501301"
XCTAssertEqual(501_301, state.amount, "AmountInput Tests: `testMaxValue` expected \(501_301) but received \(state.amount)")
}
store.receive(.updateAmount) { state in
state.amount = 501_301
}
}
@ -42,13 +43,13 @@ class TransactionAmountTextFieldTests: XCTestCase {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
currencySelectionState: CurrencySelectionState(),
maxValue: 501_301,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "0.002"
),
currencySelectionState: CurrencySelectionState(),
maxValue: 501_301
)
),
reducer: TransactionAmountTextFieldReducer.default,
environment: TransactionAmountTextFieldEnvironment()
@ -60,21 +61,21 @@ class TransactionAmountTextFieldTests: XCTestCase {
}
}
func testZecUsdConvertedAmount() throws {
try XCTSkipUnless(Locale.current.regionCode == "US", "testZecUsdConvertedAmount is designed to test US locale only")
func testZec_to_UsdConvertedAmount() throws {
try XCTSkipUnless(Locale.current.regionCode == "US", "testZec_to_UsdConvertedAmount is designed to test US locale only")
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
currencySelectionState:
CurrencySelectionState(
currencyType: .zec
),
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "1.0"
),
currencySelectionState:
CurrencySelectionState(
currencyType: .zec
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
@ -84,29 +85,59 @@ class TransactionAmountTextFieldTests: XCTestCase {
store.send(.currencySelection(.swapCurrencyType)) { state in
state.textFieldState.text = "1,000"
state.currencySelectionState.currencyType = .usd
XCTAssertEqual(
100_000_000,
state.amount,
"AmountInput Tests: `testZecUsdConvertedAmount` expected \(100_000_000) but received \(state.amount)"
)
}
store.receive(.updateAmount) { state in
state.amount = 100_000_000
}
}
func testUsdZecConvertedAmount() throws {
try XCTSkipUnless(Locale.current.regionCode == "US", "testUsdZecConvertedAmount is designed to test US locale only")
func testBigZecAmount_to_UsdConvertedAmount() throws {
try XCTSkipUnless(Locale.current.regionCode == "US", "testBigZecAmount_to_UsdConvertedAmount is designed to test US locale only")
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
currencySelectionState:
CurrencySelectionState(
currencyType: .zec
),
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "25000"
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment: TransactionAmountTextFieldEnvironment()
)
store.send(.currencySelection(.swapCurrencyType)) { state in
state.textFieldState.text = "25,000,000"
state.currencySelectionState.currencyType = .usd
}
store.receive(.updateAmount) { state in
state.amount = 2_500_000_000_000
}
}
func testUsd_to_ZecConvertedAmount() throws {
try XCTSkipUnless(Locale.current.regionCode == "US", "testUsd_to_ZecConvertedAmount is designed to test US locale only")
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
currencySelectionState:
CurrencySelectionState(
currencyType: .usd
),
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "1 000"
),
currencySelectionState:
CurrencySelectionState(
currencyType: .usd
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
@ -116,11 +147,10 @@ class TransactionAmountTextFieldTests: XCTestCase {
store.send(.currencySelection(.swapCurrencyType)) { state in
state.textFieldState.text = "1"
state.currencySelectionState.currencyType = .zec
XCTAssertEqual(
100_000_000,
state.amount,
"AmountInput Tests: `testZecUsdConvertedAmount` expected \(100_000_000) but received \(state.amount)"
)
}
store.receive(.updateAmount) { state in
state.amount = 100_000_000
}
}
@ -130,16 +160,16 @@ class TransactionAmountTextFieldTests: XCTestCase {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
currencySelectionState:
CurrencySelectionState(
currencyType: .usd
),
maxValue: 100_000_000,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
@ -150,6 +180,14 @@ class TransactionAmountTextFieldTests: XCTestCase {
state.textFieldState.text = "1 000"
state.textFieldState.valid = true
state.currencySelectionState.currencyType = .usd
XCTAssertFalse(
state.isMax,
"AmountInput Tests: `testIfAmountIsMax` is expected to be false but it's \(state.isMax)"
)
}
store.receive(.updateAmount) { state in
state.amount = 100_000_000
XCTAssertTrue(
state.isMax,
"AmountInput Tests: `testIfAmountIsMax` is expected to be true but it's \(state.isMax)"
@ -163,16 +201,16 @@ class TransactionAmountTextFieldTests: XCTestCase {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
currencySelectionState:
CurrencySelectionState(
currencyType: .zec
),
maxValue: 200_000_000,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
@ -181,11 +219,10 @@ class TransactionAmountTextFieldTests: XCTestCase {
store.send(.setMax) { state in
state.textFieldState.text = "2"
XCTAssertEqual(
200_000_000,
state.maxCurrencyConvertedValue,
"AmountInput Tests: `testMaxZecValue` expected \(200_000_000) but received \(state.maxCurrencyConvertedValue)"
)
}
store.receive(.updateAmount) { state in
state.amount = 200_000_000
}
}
@ -195,16 +232,16 @@ class TransactionAmountTextFieldTests: XCTestCase {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
currencySelectionState:
CurrencySelectionState(
currencyType: .usd
),
maxValue: 200_000_000,
textFieldState:
TCATextFieldState(
validationType: .floatingPoint,
text: "5"
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
@ -213,11 +250,10 @@ class TransactionAmountTextFieldTests: XCTestCase {
store.send(.setMax) { state in
state.textFieldState.text = "2,000"
XCTAssertEqual(
200_000_000_000,
state.maxCurrencyConvertedValue,
"AmountInput Tests: `testMaxUsdValue` expected \(200_000_000_000) but received \(state.maxCurrencyConvertedValue)"
)
}
store.receive(.updateAmount) { state in
state.amount = 200_000_000
}
}
}

View File

@ -38,15 +38,21 @@ class TransactionHistoryTests: XCTestCase {
func testSynchronizerStateChanged2Synced() throws {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: 1, status: .paid(success: false), uuid: "aa11"),
TransactionStateMockHelper(date: 1651039101, amount: 2, uuid: "bb22"),
TransactionStateMockHelper(date: 1651039000, amount: 3, status: .paid(success: true), uuid: "cc33"),
TransactionStateMockHelper(date: 1651039505, amount: 4, uuid: "dd44"),
TransactionStateMockHelper(date: 1651039404, amount: 5, uuid: "ee55"),
TransactionStateMockHelper(date: 1651039606, amount: 6, status: .paid(success: false), subtitle: "pending", uuid: "ff66"),
TransactionStateMockHelper(date: 1651039303, amount: 7, subtitle: "pending", uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: 8, status: .paid(success: true), subtitle: "pending", uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: 9, subtitle: "pending", uuid: "ii99")
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: false), uuid: "aa11"),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2), uuid: "bb22"),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .paid(success: true), uuid: "cc33"),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), uuid: "dd44"),
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5), uuid: "ee55"),
TransactionStateMockHelper(
date: 1651039606,
amount: Zatoshi(amount: 6),
status: .paid(success: false),
subtitle: "pending",
uuid: "ff66"
),
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(amount: 7), subtitle: "pending", uuid: "gg77"),
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(amount: 8), status: .paid(success: true), subtitle: "pending", uuid: "hh88"),
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(amount: 9), subtitle: "pending", uuid: "ii99")
]
let transactions = mocked.map {

View File

@ -122,9 +122,9 @@ class ZatoshiTests: XCTestCase {
// so we convert it to string, in that case we are prooving it to be rendered
// to the user exactly the way we want
XCTAssertEqual(
number.decimalString,
number.decimalString(),
"1.42857143",
"Zatoshi tests: the value is expected to be 1.42857143 but it's \(number.decimalString)"
"Zatoshi tests: the value is expected to be 1.42857143 but it's \(number.decimalString())"
)
}
@ -151,9 +151,9 @@ class ZatoshiTests: XCTestCase {
func testStringToZatoshi() throws {
if let number = Zatoshi.from(decimalString: "200.0") {
XCTAssertEqual(
number.decimalString,
number.decimalString(),
"200",
"Zatoshi tests: `testStringToZec` the value is expected to be 200 but it's \(number.decimalString)"
"Zatoshi tests: `testStringToZec` the value is expected to be 200 but it's \(number.decimalString())"
)
} else {
XCTFail("Zatoshi tests: `testStringToZatoshi` failed to convert number.")