This disables the send button when the app is syncing. but if the user is already there it won't change the underlying store to avoid unwanted re-renders by SwiftUI engine. Test reflect this situation. Also fixed a problem where the tests would not reflect the correct state from the dependency injection. Closes #611
This commit is contained in:
parent
c6b222ff46
commit
4212e4781b
|
@ -1,4 +1,6 @@
|
|||
# Changelog
|
||||
# Unreleased
|
||||
- [#611] Disable Send ZEC button when sync in progress
|
||||
# 0.0.1 build 44
|
||||
This is the baseline build for iOS Re-Scoping epic.
|
||||
- [#819] build and release from tag 0.0.1-44
|
||||
|
|
|
@ -12,19 +12,28 @@ import ZcashLightClientKit
|
|||
|
||||
extension SDKSynchronizerDependency {
|
||||
static let mock: SDKSynchronizerClient = MockSDKSynchronizerClient()
|
||||
|
||||
static func mockWithSnapshot(_ snapshot: SyncStatusSnapshot) -> MockSDKSynchronizerClient {
|
||||
MockSDKSynchronizerClient(snapshot: snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
class MockSDKSynchronizerClient: SDKSynchronizerClient {
|
||||
private var cancellables: [AnyCancellable] = []
|
||||
private var snapshot: SyncStatusSnapshot
|
||||
private(set) var notificationCenter: NotificationCenterClient
|
||||
private(set) var synchronizer: SDKSynchronizer?
|
||||
private(set) var stateChanged: CurrentValueSubject<SDKSynchronizerState, Never>
|
||||
private(set) var walletBirthday: BlockHeight?
|
||||
private(set) var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState?
|
||||
|
||||
init(notificationCenter: NotificationCenterClient = .noOp) {
|
||||
init(
|
||||
notificationCenter: NotificationCenterClient = .noOp,
|
||||
snapshot: SyncStatusSnapshot = .default
|
||||
) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.stateChanged = CurrentValueSubject<SDKSynchronizerState, Never>(.unknown)
|
||||
self.snapshot = snapshot
|
||||
}
|
||||
|
||||
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws { }
|
||||
|
@ -39,7 +48,7 @@ class MockSDKSynchronizerClient: SDKSynchronizerClient {
|
|||
|
||||
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) { }
|
||||
|
||||
func statusSnapshot() -> SyncStatusSnapshot { .default }
|
||||
func statusSnapshot() -> SyncStatusSnapshot { self.snapshot }
|
||||
|
||||
func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error>? { Empty<Void, Error>().eraseToAnyPublisher() }
|
||||
|
||||
|
|
|
@ -53,6 +53,12 @@ struct HomeReducer: ReducerProtocol {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isSendButtonDisabled: Bool {
|
||||
// If the destination is `.send` the button must be enabled
|
||||
// to avoid involuntary navigation pop.
|
||||
self.destination != .send && self.isSyncing
|
||||
}
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
|
|
|
@ -82,6 +82,8 @@ extension HomeView {
|
|||
})
|
||||
.activeButtonStyle
|
||||
.padding(.bottom, 30)
|
||||
.disabled(viewStore.isSendButtonDisabled)
|
||||
.opacity(viewStore.isSendButtonDisabled ? 0.5 : 1)
|
||||
}
|
||||
|
||||
func receiveButton(_ viewStore: HomeViewStore) -> some View {
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
@testable import secant_testnet
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class HomeTests: XCTestCase {
|
||||
func testSynchronizerStateChanged_AnyButSynced() throws {
|
||||
|
@ -32,7 +32,7 @@ class HomeTests: XCTestCase {
|
|||
reducer: HomeReducer()
|
||||
) { dependencies in
|
||||
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(.default)
|
||||
}
|
||||
|
||||
store.send(.synchronizerStateChanged(.synced))
|
||||
|
@ -42,6 +42,91 @@ class HomeTests: XCTestCase {
|
|||
store.receive(.updateSynchronizerStatus)
|
||||
}
|
||||
|
||||
func testSendButtonIsDisabledWhenSyncing() {
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let mockSnapshot = SyncStatusSnapshot.init(
|
||||
.syncing(
|
||||
.init(
|
||||
startHeight: 1_700_000,
|
||||
targetHeight: 1_800_000,
|
||||
progressHeight: 1_770_000
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: .init(
|
||||
balanceBreakdownState: .placeholder,
|
||||
profileState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
sendState: .placeholder,
|
||||
settingsState: .placeholder,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: mockSnapshot,
|
||||
walletEventsState: .emptyPlaceHolder
|
||||
),
|
||||
reducer: HomeReducer()
|
||||
)
|
||||
|
||||
store.dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
|
||||
store.dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(mockSnapshot)
|
||||
|
||||
store.send(.synchronizerStateChanged(.progressUpdated))
|
||||
|
||||
testScheduler.advance(by: 0.01)
|
||||
|
||||
store.receive(.updateSynchronizerStatus)
|
||||
|
||||
XCTAssertTrue(store.state.isSyncing)
|
||||
XCTAssertTrue(store.state.isSendButtonDisabled)
|
||||
}
|
||||
|
||||
func testSendButtonIsNotDisabledWhenSyncingWhileOnSendScreen() {
|
||||
let testScheduler = DispatchQueue.test
|
||||
|
||||
let mockSnapshot = SyncStatusSnapshot.init(
|
||||
.syncing(
|
||||
.init(
|
||||
startHeight: 1_700_000,
|
||||
targetHeight: 1_800_000,
|
||||
progressHeight: 1_770_000
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: .init(
|
||||
balanceBreakdownState: .placeholder,
|
||||
profileState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
sendState: .placeholder,
|
||||
settingsState: .placeholder,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: mockSnapshot,
|
||||
walletEventsState: .emptyPlaceHolder
|
||||
),
|
||||
reducer: HomeReducer()
|
||||
)
|
||||
|
||||
store.dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
|
||||
store.dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(mockSnapshot)
|
||||
|
||||
store.send(.updateDestination(.send)) {
|
||||
$0.destination = .send
|
||||
}
|
||||
|
||||
testScheduler.advance(by: 0.01)
|
||||
|
||||
store.send(.synchronizerStateChanged(.progressUpdated))
|
||||
|
||||
testScheduler.advance(by: 0.01)
|
||||
|
||||
store.receive(.updateSynchronizerStatus)
|
||||
|
||||
XCTAssertTrue(store.state.isSyncing)
|
||||
XCTAssertFalse(store.state.isSendButtonDisabled)
|
||||
}
|
||||
/// The .onAppear action is important to register for the synchronizer state updates.
|
||||
/// The integration tests make sure registrations and side effects are properly implemented.
|
||||
func testOnAppear() throws {
|
||||
|
@ -84,7 +169,7 @@ class HomeTests: XCTestCase {
|
|||
}
|
||||
|
||||
// long-living (cancelable) effects need to be properly canceled.
|
||||
// the .onDisappear action cancles the observer of the synchronizer status change.
|
||||
// the .onDisappear action cancels the observer of the synchronizer status change.
|
||||
store.send(.onDisappear)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class ProfileTests: XCTestCase {
|
|||
reducer: ProfileReducer()
|
||||
) { dependencies in
|
||||
dependencies.appVersion = .mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(.default)
|
||||
}
|
||||
|
||||
let uAddress = try UnifiedAddress(
|
||||
|
|
|
@ -50,7 +50,7 @@ class SendTests: XCTestCase {
|
|||
dependencies.derivationTool = .liveValue
|
||||
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
|
||||
dependencies.mnemonic = .liveValue
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(.default)
|
||||
dependencies.walletStorage = .noOp
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ class SendTests: XCTestCase {
|
|||
dependencies.derivationTool = .liveValue
|
||||
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
|
||||
dependencies.mnemonic = .liveValue
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(.default)
|
||||
dependencies.walletStorage = .noOp
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ class WalletEventsTests: XCTestCase {
|
|||
reducer: WalletEventsFlowReducer()
|
||||
) { dependencies in
|
||||
dependencies.mainQueue = Self.testScheduler.eraseToAnyScheduler()
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
|
||||
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mockWithSnapshot(.default)
|
||||
}
|
||||
|
||||
store.send(.synchronizerStateChanged(.synced))
|
||||
|
|
Loading…
Reference in New Issue