[146] [UI Component] multiple line textfield (#400)
- MultiLineTextField Store & View implemented - memo char limit is set from the zcash sdk environment - fixed tests - unit tests for the multiline textfield - send flow form validity unit tests extended to cover memo char limit
This commit is contained in:
parent
0494af83eb
commit
4f029e0ba4
|
@ -127,8 +127,11 @@
|
|||
9E6612362878345000C75B70 /* endlessCircleProgress.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E6612352878345000C75B70 /* endlessCircleProgress.json */; };
|
||||
9E66129B28884BFB00C75B70 /* LocalAuthenticationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E66129A28884BFB00C75B70 /* LocalAuthenticationHandler.swift */; };
|
||||
9E66129E288938A300C75B70 /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E66129D288938A300C75B70 /* SettingsTests.swift */; };
|
||||
9E6713F12897F81B00A6796F /* MultiLineTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */; };
|
||||
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
|
||||
9E7225F12889539300DF7F17 /* SettingsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */; };
|
||||
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */; };
|
||||
9E7225F6288AC71A00DF7F17 /* MultiLineTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */; };
|
||||
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
|
||||
9E7CB6152869E8C300A02233 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6142869E8C300A02233 /* CircularProgress.swift */; };
|
||||
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 9E7CB6172872D3DF00A02233 /* URLRouting */; };
|
||||
|
@ -351,8 +354,11 @@
|
|||
9E6612352878345000C75B70 /* endlessCircleProgress.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = endlessCircleProgress.json; sourceTree = "<group>"; };
|
||||
9E66129A28884BFB00C75B70 /* LocalAuthenticationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationHandler.swift; sourceTree = "<group>"; };
|
||||
9E66129D288938A300C75B70 /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
|
||||
9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldTests.swift; sourceTree = "<group>"; };
|
||||
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
|
||||
9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleLineTextField.swift; sourceTree = "<group>"; };
|
||||
9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldStore.swift; sourceTree = "<group>"; };
|
||||
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E7CB6142869E8C300A02233 /* CircularProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgress.swift; sourceTree = "<group>"; };
|
||||
9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
|
||||
|
@ -549,6 +555,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
9E391162284E3ECF0073DD9A /* SnapshotTests */,
|
||||
9E6713EF2897F80A00A6796F /* MultiLineTextFieldTests */,
|
||||
9E7CB6222874245400A02233 /* ProfileTests */,
|
||||
9EAB4674285B5C68002904A0 /* DeeplinkTests */,
|
||||
9E3911372848AD3A0073DD9A /* HomeTests */,
|
||||
|
@ -671,6 +678,7 @@
|
|||
2E35F99027B28E6800EB79CD /* TextFields */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E7225F4288AC6F300DF7F17 /* MultiLineTextField */,
|
||||
9E7FE0F0282E80C100C374E8 /* TCATextField */,
|
||||
2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */,
|
||||
9E5BF64C2823E84300BA3F17 /* TransactionAddress */,
|
||||
|
@ -915,6 +923,14 @@
|
|||
path = SettingsTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E6713EF2897F80A00A6796F /* MultiLineTextFieldTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */,
|
||||
);
|
||||
path = MultiLineTextFieldTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E7225EF2889537E00DF7F17 /* SettingsSnapshotTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -923,6 +939,15 @@
|
|||
path = SettingsSnapshotTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E7225F4288AC6F300DF7F17 /* MultiLineTextField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */,
|
||||
9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */,
|
||||
);
|
||||
path = MultiLineTextField;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1568,6 +1593,7 @@
|
|||
9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */,
|
||||
9E3911482848EEB90073DD9A /* RecoveryPhraseRandomizer.swift in Sources */,
|
||||
0DF482BA2787ADA800EB37D6 /* ConditionalModifier.swift in Sources */,
|
||||
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */,
|
||||
9E7FE0EC282E7D9400C374E8 /* TransactionState.swift in Sources */,
|
||||
9E2F1C8F280EDE09004E65FE /* Drawer.swift in Sources */,
|
||||
665C963F272C26E600BC04FB /* CircularFrameBackground.swift in Sources */,
|
||||
|
@ -1586,6 +1612,7 @@
|
|||
9E87ADF128363DE400122FCC /* WrappedAudioServices.swift in Sources */,
|
||||
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
|
||||
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
|
||||
9E7225F6288AC71A00DF7F17 /* MultiLineTextFieldStore.swift in Sources */,
|
||||
2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */,
|
||||
9E6612332878338C00C75B70 /* LottieAnimation.swift in Sources */,
|
||||
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
|
||||
|
@ -1660,6 +1687,7 @@
|
|||
9E7225F12889539300DF7F17 /* SettingsSnapshotTests.swift in Sources */,
|
||||
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||
9EDDEAA22829610D00B4100C /* CurrencySelectionTests.swift in Sources */,
|
||||
9E6713F12897F81B00A6796F /* MultiLineTextFieldTests.swift in Sources */,
|
||||
9E01F8282833CDA0000EFC57 /* ScanTests.swift in Sources */,
|
||||
9E66129E288938A300C75B70 /* SettingsTests.swift in Sources */,
|
||||
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */,
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
"location" : "https://github.com/zcash/ZcashLightClientKit",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c1e283837df46d734101885010185d4e093337c"
|
||||
"revision" : "fba4cecbe61cce424ada9fe1f98b05b88d5c8920"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -23,6 +23,7 @@ struct ZCashSDKEnvironment {
|
|||
let endpoint: LightWalletEndpoint
|
||||
let isMainnet: () -> Bool
|
||||
let lightWalletService: LightWalletService
|
||||
let memoCharLimit: Int
|
||||
let mnemonicWordsMaxCount: Int
|
||||
let network: ZcashNetwork
|
||||
let requiredTransactionConfirmations: Int
|
||||
|
@ -37,6 +38,7 @@ extension ZCashSDKEnvironment {
|
|||
lightWalletService: LightWalletGRPCService(
|
||||
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort)
|
||||
),
|
||||
memoCharLimit: 512,
|
||||
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
|
||||
network: ZcashNetworkBuilder.network(for: .mainnet),
|
||||
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
|
||||
|
@ -50,6 +52,7 @@ extension ZCashSDKEnvironment {
|
|||
lightWalletService: LightWalletGRPCService(
|
||||
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort)
|
||||
),
|
||||
memoCharLimit: 512,
|
||||
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
|
||||
network: ZcashNetworkBuilder.network(for: .testnet),
|
||||
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
|
||||
|
|
|
@ -339,7 +339,7 @@ extension AppReducer {
|
|||
state.homeState.route = .send
|
||||
state.homeState.sendState.amount = amount
|
||||
state.homeState.sendState.address = address
|
||||
state.homeState.sendState.memo = memo
|
||||
state.homeState.sendState.memoState.text = memo
|
||||
return .none
|
||||
|
||||
case .home(.walletEvents(.replyTo(let address))):
|
||||
|
|
|
@ -240,7 +240,8 @@ extension HomeReducer {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: environment.SDKSynchronizer,
|
||||
scheduler: environment.scheduler,
|
||||
walletStorage: environment.walletStorage
|
||||
walletStorage: environment.walletStorage,
|
||||
zcashSDKEnvironment: environment.zcashSDKEnvironment
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -36,7 +36,8 @@ struct SandboxView: View {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live()
|
||||
walletStorage: .live(),
|
||||
zcashSDKEnvironment: .mainnet
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ struct SendFlowState: Equatable {
|
|||
}
|
||||
|
||||
var isSendingTransaction = false
|
||||
var memo = ""
|
||||
var memoState: MultiLineTextFieldState
|
||||
var route: Route?
|
||||
var totalBalance = Zatoshi.zero
|
||||
var transactionAddressInputState: TransactionAddressTextFieldState
|
||||
|
@ -59,6 +59,7 @@ struct SendFlowState: Equatable {
|
|||
transactionAmountInputState.amount > 0
|
||||
&& transactionAddressInputState.isValidAddress
|
||||
&& !isInsufficientFunds
|
||||
&& memoState.isValid
|
||||
}
|
||||
|
||||
var isInsufficientFunds: Bool {
|
||||
|
@ -73,6 +74,7 @@ struct SendFlowState: Equatable {
|
|||
// MARK: - Action
|
||||
|
||||
enum SendFlowAction: Equatable {
|
||||
case memo(MultiLineTextFieldAction)
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case sendConfirmationPressed
|
||||
|
@ -81,8 +83,6 @@ enum SendFlowAction: Equatable {
|
|||
case transactionAddressInput(TransactionAddressTextFieldAction)
|
||||
case transactionAmountInput(TransactionAmountTextFieldAction)
|
||||
case updateBalance(Zatoshi)
|
||||
case updateMemo(String)
|
||||
// case updateTransaction(SendFlowTransaction)
|
||||
case updateRoute(SendFlowState.Route?)
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,7 @@ struct SendFlowEnvironment {
|
|||
let SDKSynchronizer: WrappedSDKSynchronizer
|
||||
let scheduler: AnySchedulerOf<DispatchQueue>
|
||||
let walletStorage: WrappedWalletStorage
|
||||
let zcashSDKEnvironment: ZCashSDKEnvironment
|
||||
}
|
||||
|
||||
// MARK: - Reducer
|
||||
|
@ -106,7 +107,8 @@ extension SendFlowReducer {
|
|||
[
|
||||
sendReducer,
|
||||
transactionAddressInputReducer,
|
||||
transactionAmountInputReducer
|
||||
transactionAmountInputReducer,
|
||||
memoReducer
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -145,7 +147,7 @@ extension SendFlowReducer {
|
|||
with: spendingKey,
|
||||
zatoshi: state.amount,
|
||||
to: state.address,
|
||||
memo: state.memo,
|
||||
memo: state.memoState.text,
|
||||
from: 0
|
||||
)
|
||||
.receive(on: environment.scheduler)
|
||||
|
@ -171,6 +173,7 @@ extension SendFlowReducer {
|
|||
return .none
|
||||
|
||||
case .onAppear:
|
||||
state.memoState.charLimit = environment.zcashSDKEnvironment.memoCharLimit
|
||||
return environment.SDKSynchronizer.stateChanged
|
||||
.map(SendFlowAction.synchronizerStateChanged)
|
||||
.eraseToEffect()
|
||||
|
@ -194,8 +197,7 @@ extension SendFlowReducer {
|
|||
state.transactionAmountInputState.maxValue = balance.amount
|
||||
return .none
|
||||
|
||||
case .updateMemo(let memo):
|
||||
state.memo = memo
|
||||
case .memo:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +221,13 @@ extension SendFlowReducer {
|
|||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
private static let memoReducer: SendFlowReducer = MultiLineTextFieldReducer.default.pullback(
|
||||
state: \SendFlowState.memoState,
|
||||
action: /SendFlowAction.memo,
|
||||
environment: { _ in MultiLineTextFieldEnvironment() }
|
||||
)
|
||||
|
||||
static func `default`(whenDone: @escaping () -> Void) -> SendFlowReducer {
|
||||
SendFlowReducer { state, action, environment in
|
||||
switch action {
|
||||
|
@ -232,6 +240,17 @@ extension SendFlowReducer {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Store
|
||||
|
||||
extension SendFlowStore {
|
||||
func memoStore() -> MultiLineTextFieldStore {
|
||||
self.scope(
|
||||
state: \.memoState,
|
||||
action: SendFlowAction.memo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewStore
|
||||
|
||||
extension SendFlowViewStore {
|
||||
|
@ -269,13 +288,6 @@ extension SendFlowViewStore {
|
|||
embed: { $0 ? SendFlowState.Route.done : SendFlowState.Route.confirmation }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForMemo: Binding<String> {
|
||||
self.binding(
|
||||
get: \.memo,
|
||||
send: SendFlowAction.updateMemo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
@ -283,6 +295,7 @@ extension SendFlowViewStore {
|
|||
extension SendFlowState {
|
||||
static var placeholder: Self {
|
||||
.init(
|
||||
memoState: .placeholder,
|
||||
route: nil,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .amount
|
||||
|
@ -291,6 +304,7 @@ extension SendFlowState {
|
|||
|
||||
static var emptyPlaceholder: Self {
|
||||
.init(
|
||||
memoState: .placeholder,
|
||||
route: nil,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
|
@ -303,6 +317,7 @@ extension SendFlowStore {
|
|||
static var placeholder: SendFlowStore {
|
||||
return SendFlowStore(
|
||||
initialState: .init(
|
||||
memoState: .placeholder,
|
||||
route: nil,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
|
@ -314,7 +329,8 @@ extension SendFlowStore {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live()
|
||||
walletStorage: .live(),
|
||||
zcashSDKEnvironment: .mainnet
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ struct SendFLowView_Previews: PreviewProvider {
|
|||
SendFlowView(
|
||||
store: .init(
|
||||
initialState: .init(
|
||||
memoState: .placeholder,
|
||||
route: nil,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
|
@ -45,7 +46,8 @@ struct SendFLowView_Previews: PreviewProvider {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
|
||||
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
walletStorage: .live()
|
||||
walletStorage: .live(),
|
||||
zcashSDKEnvironment: .mainnet
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -62,13 +62,12 @@ struct CreateTransaction: View {
|
|||
}
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
Text("Memo")
|
||||
|
||||
TextEditor(text: viewStore.bindingForMemo)
|
||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
||||
.importSeedEditorModifier(Asset.Colors.Text.activeButtonText.color)
|
||||
}
|
||||
MultipleLineTextField(
|
||||
store: store.memoStore(),
|
||||
title: "Memo",
|
||||
titleAccessoryView: {}
|
||||
)
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
|
||||
Button(
|
||||
|
|
|
@ -20,7 +20,7 @@ struct TransactionSent: View {
|
|||
|
||||
Text("amount: \(viewStore.amount.decimalString())")
|
||||
+ Text(" address: \(viewStore.address)")
|
||||
+ Text(" memo: \(viewStore.memo)")
|
||||
+ Text(" memo: \(viewStore.memoState.text)")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2A",
|
||||
"red" : "0xA7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2A",
|
||||
"red" : "0xA7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xA0",
|
||||
"green" : "0x81",
|
||||
"red" : "0x6E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -110,6 +110,7 @@ internal enum Asset {
|
|||
internal static let drawerTabsText = ColorAsset(name: "DrawerTabsText")
|
||||
internal static let heading = ColorAsset(name: "Heading")
|
||||
internal static let importSeedEditor = ColorAsset(name: "ImportSeedEditor")
|
||||
internal static let invalidEntry = ColorAsset(name: "InvalidEntry")
|
||||
internal static let medium = ColorAsset(name: "Medium")
|
||||
internal static let regular = ColorAsset(name: "Regular")
|
||||
internal static let secondaryButtonText = ColorAsset(name: "SecondaryButtonText")
|
||||
|
@ -124,6 +125,7 @@ internal enum Asset {
|
|||
internal static let moreInfoText = ColorAsset(name: "moreInfoText")
|
||||
}
|
||||
internal enum TextField {
|
||||
internal static let multilineOutline = ColorAsset(name: "MultilineOutline")
|
||||
internal static let titleAccessoryButton = ColorAsset(name: "TitleAccessoryButton")
|
||||
internal static let titleAccessoryButtonPressed = ColorAsset(name: "TitleAccessoryButtonPressed")
|
||||
internal enum Underline {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// MultiLineTextFieldStore.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 22.07.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
typealias MultiLineTextFieldReducer = Reducer<MultiLineTextFieldState, MultiLineTextFieldAction, MultiLineTextFieldEnvironment>
|
||||
typealias MultiLineTextFieldStore = Store<MultiLineTextFieldState, MultiLineTextFieldAction>
|
||||
typealias MultiLineTextFieldViewStore = ViewStore<MultiLineTextFieldState, MultiLineTextFieldAction>
|
||||
|
||||
// MARK: - State
|
||||
|
||||
struct MultiLineTextFieldState: Equatable {
|
||||
/// default 0, no char limit
|
||||
var charLimit = 0
|
||||
@BindableState var text = ""
|
||||
|
||||
var isCharLimited: Bool {
|
||||
charLimit > 0
|
||||
}
|
||||
|
||||
var textLength: Int {
|
||||
text.count
|
||||
}
|
||||
|
||||
var isValid: Bool {
|
||||
charLimit > 0
|
||||
? textLength <= charLimit
|
||||
: true
|
||||
}
|
||||
|
||||
var charLimitText: String {
|
||||
charLimit > 0
|
||||
? isValid
|
||||
? "\(textLength)/\(charLimit)"
|
||||
: "char limit exceeded \(textLength)/\(charLimit)"
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
enum MultiLineTextFieldAction: Equatable, BindableAction {
|
||||
case binding(BindingAction<MultiLineTextFieldState>)
|
||||
}
|
||||
|
||||
// MARK: - Environment
|
||||
|
||||
struct MultiLineTextFieldEnvironment { }
|
||||
|
||||
extension MultiLineTextFieldEnvironment {
|
||||
static let live = MultiLineTextFieldEnvironment()
|
||||
|
||||
static let mock = MultiLineTextFieldEnvironment()
|
||||
}
|
||||
|
||||
// MARK: - Reducer
|
||||
|
||||
extension MultiLineTextFieldReducer {
|
||||
static let `default` = MultiLineTextFieldReducer { _, action, _ in
|
||||
switch action {
|
||||
case .binding(\.$text):
|
||||
return .none
|
||||
|
||||
case .binding:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
.binding()
|
||||
}
|
||||
|
||||
// MARK: - Store
|
||||
|
||||
extension MultiLineTextFieldStore {
|
||||
static let placeholder = MultiLineTextFieldStore(
|
||||
initialState: .placeholder,
|
||||
reducer: .default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
extension MultiLineTextFieldState {
|
||||
static let placeholder = MultiLineTextFieldState()
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// MultipleLineTextField.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 22.07.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct MultipleLineTextField<TitleAccessoryContent>: View
|
||||
where TitleAccessoryContent: View {
|
||||
let store: MultiLineTextFieldStore
|
||||
let title: String
|
||||
|
||||
@ViewBuilder let titleAccessoryView: TitleAccessoryContent
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack {
|
||||
HStack {
|
||||
Text(title)
|
||||
.font(.custom(FontFamily.Rubik.regular.name, size: 13))
|
||||
Spacer()
|
||||
titleAccessoryView
|
||||
}
|
||||
|
||||
TextEditor(text: viewStore.binding(\.$text))
|
||||
.multilineTextEditorModifier(
|
||||
Asset.Colors.Text.activeButtonText.color,
|
||||
Asset.Colors.TextField.multilineOutline.color
|
||||
)
|
||||
|
||||
if viewStore.isCharLimited {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewStore.charLimitText)
|
||||
.font(.custom(FontFamily.Rubik.regular.name, size: 14))
|
||||
.foregroundColor(
|
||||
viewStore.isValid
|
||||
? Asset.Colors.TextField.multilineOutline.color
|
||||
: Asset.Colors.Text.invalidEntry.color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear(perform: { UITextView.appearance().backgroundColor = .clear })
|
||||
}
|
||||
}
|
||||
|
||||
struct MultilineTextEditorModifier: ViewModifier {
|
||||
var backgroundColor = Color.white
|
||||
var outlineColor = Color.black
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
.padding()
|
||||
.background(backgroundColor)
|
||||
.cornerRadius(4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.stroke(outlineColor, lineWidth: 2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func multilineTextEditorModifier(
|
||||
_ backgroundColor: Color = .white,
|
||||
_ outlineColor: Color = .black
|
||||
) -> some View {
|
||||
modifier(
|
||||
MultilineTextEditorModifier(
|
||||
backgroundColor: backgroundColor,
|
||||
outlineColor: outlineColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct MultipleLineTextField_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MultipleLineTextField(
|
||||
store: .placeholder,
|
||||
title: "Memo",
|
||||
titleAccessoryView: {
|
||||
Text("accessory")
|
||||
.font(.custom(FontFamily.Rubik.regular.name, size: 13))
|
||||
}
|
||||
)
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ class DeeplinkTests: XCTestCase {
|
|||
state.homeState.route = .send
|
||||
state.homeState.sendState.amount = amount
|
||||
state.homeState.sendState.address = address
|
||||
state.homeState.sendState.memo = memo
|
||||
state.homeState.sendState.memoState.text = memo
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ class DeeplinkTests: XCTestCase {
|
|||
state.homeState.route = .send
|
||||
state.homeState.sendState.amount = amount
|
||||
state.homeState.sendState.address = address
|
||||
state.homeState.sendState.memo = memo
|
||||
state.homeState.sendState.memoState.text = memo
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +208,7 @@ class DeeplinkTests: XCTestCase {
|
|||
state.homeState.route = .send
|
||||
state.homeState.sendState.amount = amount
|
||||
state.homeState.sendState.address = address
|
||||
state.homeState.sendState.memo = memo
|
||||
state.homeState.sendState.memoState.text = memo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// MultiLineTextFieldTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 01.08.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import ComposableArchitecture
|
||||
|
||||
class MultiLineTextFieldTests: XCTestCase {
|
||||
func testIsCharLimited() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(charLimit: 1),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertTrue(
|
||||
state.isCharLimited,
|
||||
"Multiline TextFiler tests: `testIsCharLimited` is expected to be true but it is \(state.isCharLimited)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testIsNotCharLimited() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertFalse(
|
||||
state.isCharLimited,
|
||||
"Multiline TextFiler tests: `testIsNotCharLimited` is expected to be false but it is \(state.isCharLimited)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testTextLength() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertEqual(
|
||||
4,
|
||||
state.textLength,
|
||||
"Multiline TextFiler tests: `testTextLength` is expected to be 4 but it is \(state.textLength)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testIsValid_CharLimit() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(charLimit: 4),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertTrue(
|
||||
state.isValid,
|
||||
"Multiline TextFiler tests: `testIsValid_CharLimit` is expected to be true but it is \(state.isValid)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testIsValid_NoCharLimit() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertTrue(
|
||||
state.isValid,
|
||||
"Multiline TextFiler tests: `testIsValid_NoCharLimit` is expected to be true but it is \(state.isValid)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testIsInvalid() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(charLimit: 3),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertFalse(
|
||||
state.isValid,
|
||||
"Multiline TextFiler tests: `testIsInvalid` is expected to be false but it is \(state.isValid)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testCharLimitText_NoCharLimit() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertEqual(
|
||||
"",
|
||||
state.charLimitText,
|
||||
"Multiline TextFiler tests: `testCharLimitText_NoCharLimit` is expected to be \"\" but it is \(state.charLimitText)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testCharLimitText_CharLimit_LessCharacters() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(charLimit: 5),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertEqual(
|
||||
"4/5",
|
||||
state.charLimitText,
|
||||
"Multiline TextFiler tests: `testCharLimitText_CharLimit_LessCharacters` is expected to be \"4/5\" but it is \(state.charLimitText)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testCharLimitText_CharLimit_Exceeded() throws {
|
||||
let store = TestStore(
|
||||
initialState: MultiLineTextFieldState(charLimit: 3),
|
||||
reducer: MultiLineTextFieldReducer.default,
|
||||
environment: MultiLineTextFieldEnvironment()
|
||||
)
|
||||
|
||||
store.send(.binding(.set(\.$text, "test"))) { state in
|
||||
state.text = "test"
|
||||
XCTAssertEqual(
|
||||
"char limit exceeded 4/3",
|
||||
state.charLimitText,
|
||||
"Multiline TextFiler tests: `testCharLimitText_CharLimit_Exceeded` is expected to be \"4/5\" but it is \(state.charLimitText)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,7 +40,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: MockWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: storage)
|
||||
walletStorage: .live(walletStorage: storage),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -97,7 +98,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: storage)
|
||||
walletStorage: .live(walletStorage: storage),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -137,7 +139,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -180,7 +183,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -205,7 +209,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -230,6 +235,7 @@ class SendTests: XCTestCase {
|
|||
|
||||
func testFundsSufficiency() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
|
@ -251,7 +257,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(numberFormatter: usNumberFormatter),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -300,11 +307,13 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(numberFormatter: usNumberFormatter),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: .init(
|
||||
memoState: .placeholder,
|
||||
route: nil,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
|
@ -337,6 +346,7 @@ class SendTests: XCTestCase {
|
|||
|
||||
func testValidForm() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
|
@ -359,7 +369,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -383,6 +394,7 @@ class SendTests: XCTestCase {
|
|||
|
||||
func testInvalidForm_InsufficientFunds() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
|
@ -404,7 +416,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -428,6 +441,7 @@ class SendTests: XCTestCase {
|
|||
|
||||
func testInvalidForm_AddressFormat() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
|
@ -449,7 +463,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -473,6 +488,7 @@ class SendTests: XCTestCase {
|
|||
|
||||
func testInvalidForm_AmountFormat() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
|
@ -494,7 +510,8 @@ class SendTests: XCTestCase {
|
|||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live))
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
|
@ -515,6 +532,104 @@ class SendTests: XCTestCase {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidForm_ExceededMemoCharLimit() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: MultiLineTextFieldState(charLimit: 3),
|
||||
totalBalance: Zatoshi(1),
|
||||
transactionAddressInputState:
|
||||
TransactionAddressTextFieldState(
|
||||
isValidAddress: true,
|
||||
textFieldState:
|
||||
TCATextFieldState(
|
||||
validationType: .none,
|
||||
text: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||
)
|
||||
),
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
amount: 100,
|
||||
currencySelectionState: CurrencySelectionState(),
|
||||
maxValue: 501_302,
|
||||
textFieldState:
|
||||
TCATextFieldState(
|
||||
validationType: .floatingPoint,
|
||||
text: "0.0.0501301"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let testEnvironment = SendFlowEnvironment(
|
||||
derivationTool: .live(),
|
||||
mnemonic: .mock,
|
||||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: sendState,
|
||||
reducer: SendFlowReducer.default,
|
||||
environment: testEnvironment
|
||||
)
|
||||
|
||||
store.send(.memo(.binding(.set(\.$text, "test")))) { state in
|
||||
state.memoState.text = "test"
|
||||
XCTAssertFalse(
|
||||
state.isValidForm,
|
||||
"Send Tests: `testValidForm` is expected to be false but it's \(state.isValidForm)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testMemoCharLimitSet() throws {
|
||||
let sendState = SendFlowState(
|
||||
memoState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldState(
|
||||
currencySelectionState: CurrencySelectionState(),
|
||||
maxValue: 501_302,
|
||||
textFieldState:
|
||||
TCATextFieldState(
|
||||
validationType: .floatingPoint,
|
||||
text: "0.0.0501301"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let testEnvironment = SendFlowEnvironment(
|
||||
derivationTool: .live(),
|
||||
mnemonic: .mock,
|
||||
numberFormatter: .live(),
|
||||
SDKSynchronizer: TestWrappedSDKSynchronizer(),
|
||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||
zcashSDKEnvironment: .testnet
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: sendState,
|
||||
reducer: SendFlowReducer.default,
|
||||
environment: testEnvironment
|
||||
)
|
||||
|
||||
store.send(.onAppear) { state in
|
||||
state.memoState.charLimit = 512
|
||||
}
|
||||
|
||||
store.receive(.synchronizerStateChanged(.unknown))
|
||||
|
||||
// .onAppear action starts long living cancelable action .synchronizerStateChanged
|
||||
// .onDisappear cancels it, must have for the test to pass
|
||||
store.send(.onDisappear)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SendTests {
|
||||
|
|
Loading…
Reference in New Issue