[#557] Nav Changes (#602)

- previous profile screen connected to the receive ZEC button
- receive ZEC is now simplified to show only QR code + UA with small "i" icon leading to address details
- profile's UA address copy to pasteboard added
- home's settings button connected to settings screen
- settings screen updated, test crash report and rescan blockchain moved to debug menu
- root reducer's debug code move to a separate file
- unit tests updated + debug tests provided
This commit is contained in:
Lukas Korba 2023-03-02 15:24:32 +01:00 committed by GitHub
parent f1c9b06123
commit 49d858d22a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 384 additions and 381 deletions

View File

@ -447,6 +447,9 @@
9E7FE0F628327F6F00C374E8 /* ScanUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */; }; 9E7FE0F628327F6F00C374E8 /* ScanUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */; };
9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */; }; 9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */; };
9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */; }; 9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */; };
9E852D6129B098F400CF4AC1 /* RootDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D6029B098F400CF4AC1 /* RootDebug.swift */; };
9E852D6229B098F400CF4AC1 /* RootDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D6029B098F400CF4AC1 /* RootDebug.swift */; };
9E852D6529B0A86300CF4AC1 /* DebugTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D6429B0A86300CF4AC1 /* DebugTests.swift */; };
9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E92AF0728530EBF007367AD /* View+UIImage.swift */; }; 9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E92AF0728530EBF007367AD /* View+UIImage.swift */; };
9E94C62028AA7DEE008256E9 /* BalanceBreakdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */; }; 9E94C62028AA7DEE008256E9 /* BalanceBreakdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */; };
9E94C62328AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */; }; 9E94C62328AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */; };
@ -770,6 +773,8 @@
9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanUIView.swift; sourceTree = "<group>"; }; 9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanUIView.swift; sourceTree = "<group>"; };
9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScanView.swift; sourceTree = "<group>"; }; 9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScanView.swift; sourceTree = "<group>"; };
9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidationFlowFeatureFlagTests.swift; sourceTree = "<group>"; }; 9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidationFlowFeatureFlagTests.swift; sourceTree = "<group>"; };
9E852D6029B098F400CF4AC1 /* RootDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootDebug.swift; sourceTree = "<group>"; };
9E852D6429B0A86300CF4AC1 /* DebugTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTests.swift; sourceTree = "<group>"; };
9E92AF0728530EBF007367AD /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = "<group>"; }; 9E92AF0728530EBF007367AD /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = "<group>"; };
9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownTests.swift; sourceTree = "<group>"; }; 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownTests.swift; sourceTree = "<group>"; };
9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownSnapshotTests.swift; sourceTree = "<group>"; }; 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownSnapshotTests.swift; sourceTree = "<group>"; };
@ -1852,6 +1857,7 @@
children = ( children = (
9EAFEB812805793200199FC9 /* RootTests.swift */, 9EAFEB812805793200199FC9 /* RootTests.swift */,
9E391131284644580073DD9A /* AppInitializationTests.swift */, 9E391131284644580073DD9A /* AppInitializationTests.swift */,
9E852D6429B0A86300CF4AC1 /* DebugTests.swift */,
); );
path = RootTests; path = RootTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2104,6 +2110,7 @@
F9971A4C27680DC400A2DB75 /* RootView.swift */, F9971A4C27680DC400A2DB75 /* RootView.swift */,
9E9ADA7E2938F5EC0071767B /* RootDestination.swift */, 9E9ADA7E2938F5EC0071767B /* RootDestination.swift */,
9E9ADA7C2938F4C00071767B /* RootInitialization.swift */, 9E9ADA7C2938F4C00071767B /* RootInitialization.swift */,
9E852D6029B098F400CF4AC1 /* RootDebug.swift */,
); );
path = Root; path = Root;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2686,6 +2693,7 @@
0D26AF01299E8196005260EE /* RecoveryPhraseDisplayView.swift in Sources */, 0D26AF01299E8196005260EE /* RecoveryPhraseDisplayView.swift in Sources */,
0D26AF02299E8196005260EE /* URIParser.swift in Sources */, 0D26AF02299E8196005260EE /* URIParser.swift in Sources */,
0D26AF03299E8196005260EE /* URIParserLive.swift in Sources */, 0D26AF03299E8196005260EE /* URIParserLive.swift in Sources */,
9E852D6229B098F400CF4AC1 /* RootDebug.swift in Sources */,
34F682ED29A763FD0022C079 /* WalletConfigProvider.swift in Sources */, 34F682ED29A763FD0022C079 /* WalletConfigProvider.swift in Sources */,
0D26AF04299E8196005260EE /* LocalAuthenticationTestKey.swift in Sources */, 0D26AF04299E8196005260EE /* LocalAuthenticationTestKey.swift in Sources */,
0D26AF05299E8196005260EE /* ScanView.swift in Sources */, 0D26AF05299E8196005260EE /* ScanView.swift in Sources */,
@ -2915,6 +2923,7 @@
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */, 0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */,
9EB863A2292398A8003D0F8B /* URIParser.swift in Sources */, 9EB863A2292398A8003D0F8B /* URIParser.swift in Sources */,
9EB863C12923C779003D0F8B /* URIParserLive.swift in Sources */, 9EB863C12923C779003D0F8B /* URIParserLive.swift in Sources */,
9E852D6129B098F400CF4AC1 /* RootDebug.swift in Sources */,
34F682EC29A763FD0022C079 /* WalletConfigProvider.swift in Sources */, 34F682EC29A763FD0022C079 /* WalletConfigProvider.swift in Sources */,
9EBDF987291F91EF000A1A05 /* LocalAuthenticationTestKey.swift in Sources */, 9EBDF987291F91EF000A1A05 /* LocalAuthenticationTestKey.swift in Sources */,
F9971A5F27680DF600A2DB75 /* ScanView.swift in Sources */, F9971A5F27680DF600A2DB75 /* ScanView.swift in Sources */,
@ -3053,6 +3062,7 @@
9EAFEB862805A23100199FC9 /* SecItemClientTests.swift in Sources */, 9EAFEB862805A23100199FC9 /* SecItemClientTests.swift in Sources */,
3448CB3728E485CB006ADEDB /* NotEnoughFeeSpaceSnapshots.swift in Sources */, 3448CB3728E485CB006ADEDB /* NotEnoughFeeSpaceSnapshots.swift in Sources */,
9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */, 9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */,
9E852D6529B0A86300CF4AC1 /* DebugTests.swift in Sources */,
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */, 9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */,
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */, 9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */, 9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */,

View File

@ -4,9 +4,11 @@
// //
// Created by Francisco Gindre on 2/2/23. // Created by Francisco Gindre on 2/2/23.
// //
import ComposableArchitecture import ComposableArchitecture
import FirebaseCore import FirebaseCore
import FirebaseCrashlytics import FirebaseCrashlytics
extension CrashReporterClient: DependencyKey { extension CrashReporterClient: DependencyKey {
static let liveValue = CrashReporterClient( static let liveValue = CrashReporterClient(
configure: { canConfigure in configure: { canConfigure in

View File

@ -6,6 +6,7 @@
// //
import ComposableArchitecture import ComposableArchitecture
extension CrashReporterClient: TestDependencyKey { extension CrashReporterClient: TestDependencyKey {
static let testValue = CrashReporterClient( static let testValue = CrashReporterClient(
configure: { _ in }, configure: { _ in },

View File

@ -4,6 +4,7 @@
// //
// Created by Francisco Gindre on 2/2/23. // Created by Francisco Gindre on 2/2/23.
// //
import ComposableArchitecture import ComposableArchitecture
import Foundation import Foundation

View File

@ -16,8 +16,8 @@ struct HomeReducer: ReducerProtocol {
case balanceBreakdown case balanceBreakdown
case notEnoughFreeDiskSpace case notEnoughFreeDiskSpace
case profile case profile
case request
case send case send
case settings
case transactionHistory case transactionHistory
} }
@ -25,10 +25,10 @@ struct HomeReducer: ReducerProtocol {
var balanceBreakdownState: BalanceBreakdownReducer.State var balanceBreakdownState: BalanceBreakdownReducer.State
var destination: Destination? var destination: Destination?
var profileState: ProfileReducer.State var profileState: ProfileReducer.State
var requestState: RequestReducer.State
var requiredTransactionConfirmations = 0 var requiredTransactionConfirmations = 0
var scanState: ScanReducer.State var scanState: ScanReducer.State
var sendState: SendFlowReducer.State var sendState: SendFlowReducer.State
var settingsState: SettingsReducer.State
var shieldedBalance: Balance var shieldedBalance: Balance
var synchronizerStatusSnapshot: SyncStatusSnapshot var synchronizerStatusSnapshot: SyncStatusSnapshot
var walletEventsState: WalletEventsFlowReducer.State var walletEventsState: WalletEventsFlowReducer.State
@ -61,9 +61,8 @@ struct HomeReducer: ReducerProtocol {
case onAppear case onAppear
case onDisappear case onDisappear
case profile(ProfileReducer.Action) case profile(ProfileReducer.Action)
case request(RequestReducer.Action)
case rewindDone(String?, SettingsReducer.Action)
case send(SendFlowReducer.Action) case send(SendFlowReducer.Action)
case settings(SettingsReducer.Action)
case synchronizerStateChanged(SDKSynchronizerState) case synchronizerStateChanged(SDKSynchronizerState)
case walletEvents(WalletEventsFlowReducer.Action) case walletEvents(WalletEventsFlowReducer.Action)
case updateDestination(HomeReducer.State.Destination?) case updateDestination(HomeReducer.State.Destination?)
@ -86,6 +85,10 @@ struct HomeReducer: ReducerProtocol {
SendFlowReducer() SendFlowReducer()
} }
Scope(state: \.settingsState, action: /Action.settings) {
SettingsReducer()
}
Scope(state: \.profileState, action: /Action.profile) { Scope(state: \.profileState, action: /Action.profile) {
ProfileReducer() ProfileReducer()
} }
@ -138,45 +141,12 @@ struct HomeReducer: ReducerProtocol {
state.destination = nil state.destination = nil
return .none return .none
case .profile(.settings(.quickRescan)): case .settings:
state.destination = nil return .none
return .run { send in
do {
try await sdkSynchronizer.rewind(.quick)
await send(.rewindDone(nil, .quickRescan))
} catch {
await send(.rewindDone(error.localizedDescription, .quickRescan))
}
}
case .profile(.settings(.fullRescan)):
state.destination = nil
return .run { send in
do {
try await sdkSynchronizer.rewind(.birthday)
await send(.rewindDone(nil, .fullRescan))
} catch {
await send(.rewindDone(error.localizedDescription, .fullRescan))
}
}
case .profile: case .profile:
return .none return .none
case .request:
return .none
case let .rewindDone(errorDescription, _):
if let errorDescription {
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState("Rewind failed"),
message: TextState("Error: \(errorDescription)"),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
)
}
return .none
case .walletEvents: case .walletEvents:
return .none return .none
@ -221,13 +191,6 @@ extension HomeStore {
) )
} }
func requestStore() -> RequestStore {
self.scope(
state: \.requestState,
action: HomeReducer.Action.request
)
}
func sendStore() -> SendFlowStore { func sendStore() -> SendFlowStore {
self.scope( self.scope(
state: \.sendState, state: \.sendState,
@ -235,6 +198,13 @@ extension HomeStore {
) )
} }
func settingsStore() -> SettingsStore {
self.scope(
state: \.settingsState,
action: HomeReducer.Action.settings
)
}
func balanceBreakdownStore() -> BalanceBreakdownStore { func balanceBreakdownStore() -> BalanceBreakdownStore {
self.scope( self.scope(
state: \.balanceBreakdownState, state: \.balanceBreakdownState,
@ -263,9 +233,9 @@ extension HomeReducer.State {
.init( .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: Balance.zero, shieldedBalance: Balance.zero,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .emptyPlaceHolder walletEventsState: .emptyPlaceHolder

View File

@ -8,9 +8,9 @@ struct HomeView: View {
WithViewStore(store) { viewStore in WithViewStore(store) { viewStore in
VStack { VStack {
HStack { HStack {
profileButton(viewStore)
Spacer() Spacer()
settingsButton(viewStore)
} }
balance(viewStore) balance(viewStore)
@ -19,6 +19,8 @@ struct HomeView: View {
sendButton(viewStore) sendButton(viewStore)
receiveButton(viewStore)
Button { Button {
viewStore.send(.updateDestination(.transactionHistory)) viewStore.send(.updateDestination(.transactionHistory))
} label: { } label: {
@ -48,21 +50,21 @@ struct HomeView: View {
// MARK: - Buttons // MARK: - Buttons
extension HomeView { extension HomeView {
func profileButton(_ viewStore: HomeViewStore) -> some View { func settingsButton(_ viewStore: HomeViewStore) -> some View {
Image(Asset.Assets.Icons.profile.name) Image(Asset.Assets.Icons.profile.name)
.resizable() .resizable()
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.padding(.trailing, 15) .padding(.trailing, 15)
.navigationLink( .navigationLink(
isActive: viewStore.bindingForDestination(.profile), isActive: viewStore.bindingForDestination(.settings),
destination: { destination: {
ProfileView(store: store.profileStore()) SettingsView(store: store.settingsStore())
} }
) )
} }
func sendButton(_ viewStore: HomeViewStore) -> some View { func sendButton(_ viewStore: HomeViewStore) -> some View {
Text("Send") Text("Send ZEC")
.shadow(color: Asset.Colors.Buttons.buttonsTitleShadow.color, radius: 2, x: 0, y: 2) .shadow(color: Asset.Colors.Buttons.buttonsTitleShadow.color, radius: 2, x: 0, y: 2)
.frame( .frame(
minWidth: 0, minWidth: 0,
@ -85,6 +87,30 @@ extension HomeView {
.padding(.bottom, 30) .padding(.bottom, 30)
} }
func receiveButton(_ viewStore: HomeViewStore) -> some View {
Text("Receive ZEC")
.shadow(color: Asset.Colors.Buttons.buttonsTitleShadow.color, radius: 2, x: 0, y: 2)
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity
)
.foregroundColor(Asset.Colors.Text.activeButtonText.color)
.background(Asset.Colors.Buttons.activeButton.color)
.cornerRadius(12)
.frame(height: 60)
.padding(.horizontal, 50)
.neumorphicButton()
.navigationLink(
isActive: viewStore.bindingForDestination(.profile),
destination: {
ProfileView(store: store.profileStore())
}
)
.padding(.bottom, 30)
}
func balance(_ viewStore: HomeViewStore) -> some View { func balance(_ viewStore: HomeViewStore) -> some View {
Group { Group {
Button { Button {

View File

@ -9,7 +9,6 @@ struct ProfileReducer: ReducerProtocol {
struct State: Equatable { struct State: Equatable {
enum Destination { enum Destination {
case addressDetails case addressDetails
case settings
} }
var addressDetailsState: AddressDetailsReducer.State var addressDetailsState: AddressDetailsReducer.State
@ -17,7 +16,6 @@ struct ProfileReducer: ReducerProtocol {
var appVersion = "" var appVersion = ""
var destination: Destination? var destination: Destination?
var sdkVersion = "" var sdkVersion = ""
var settingsState: SettingsReducer.State
var unifiedAddress: String { var unifiedAddress: String {
addressDetailsState.uAddress?.stringEncoded ?? "could not extract UA" addressDetailsState.uAddress?.stringEncoded ?? "could not extract UA"
@ -27,12 +25,13 @@ struct ProfileReducer: ReducerProtocol {
enum Action: Equatable { enum Action: Equatable {
case addressDetails(AddressDetailsReducer.Action) case addressDetails(AddressDetailsReducer.Action)
case back case back
case copyUnifiedAddressToPastboard
case onAppear case onAppear
case settings(SettingsReducer.Action)
case updateDestination(ProfileReducer.State.Destination?) case updateDestination(ProfileReducer.State.Destination?)
} }
@Dependency(\.appVersion) var appVersion @Dependency(\.appVersion) var appVersion
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.sdkSynchronizer) var sdkSynchronizer @Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment @Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
@ -41,10 +40,6 @@ struct ProfileReducer: ReducerProtocol {
AddressDetailsReducer() AddressDetailsReducer()
} }
Scope(state: \.settingsState, action: /Action.settings) {
SettingsReducer()
}
Reduce { state, action in Reduce { state, action in
switch action { switch action {
case .onAppear: case .onAppear:
@ -57,15 +52,16 @@ struct ProfileReducer: ReducerProtocol {
case .back: case .back:
return .none return .none
case .copyUnifiedAddressToPastboard:
pasteboard.setString(state.unifiedAddress.redacted)
return .none
case let .updateDestination(destination): case let .updateDestination(destination):
state.destination = destination state.destination = destination
return .none return .none
case .addressDetails: case .addressDetails:
return .none return .none
case .settings:
return .none
} }
} }
} }
@ -80,13 +76,6 @@ extension ProfileStore {
action: ProfileReducer.Action.addressDetails action: ProfileReducer.Action.addressDetails
) )
} }
func settingsStore() -> SettingsStore {
self.scope(
state: \.settingsState,
action: ProfileReducer.Action.settings
)
}
} }
// MARK: - ViewStore // MARK: - ViewStore
@ -105,13 +94,6 @@ extension ProfileViewStore {
embed: { $0 ? .addressDetails : nil } embed: { $0 ? .addressDetails : nil }
) )
} }
var bindingForSettings: Binding<Bool> {
self.destinationBinding.map(
extract: { $0 == .settings },
embed: { $0 ? .settings : nil }
)
}
} }
// MARK: - Placeholders // MARK: - Placeholders
@ -120,8 +102,7 @@ extension ProfileReducer.State {
static var placeholder: Self { static var placeholder: Self {
.init( .init(
addressDetailsState: .placeholder, addressDetailsState: .placeholder,
destination: nil, destination: nil
settingsState: .placeholder
) )
} }
} }

View File

@ -6,62 +6,32 @@ struct ProfileView: View {
var body: some View { var body: some View {
WithViewStore(store) { viewStore in WithViewStore(store) { viewStore in
ScrollView { VStack {
qrCodeUA(viewStore.unifiedAddress) qrCodeUA(viewStore.unifiedAddress)
.padding(.top, 30) .padding(.vertical, 50)
Text("Your UA address \(viewStore.unifiedAddress)")
.truncationMode(.middle)
.multilineTextAlignment(.center)
.lineLimit(2)
.padding(30)
Button(
action: { viewStore.send(.updateDestination(.addressDetails)) },
label: { Text("See address details") }
)
.activeButtonStyle
.frame(height: 50)
.padding(EdgeInsets(top: 0, leading: 30, bottom: 50, trailing: 30))
Rectangle()
.frame(height: 1.5)
.padding(EdgeInsets(top: 0, leading: 100, bottom: 20, trailing: 100))
.foregroundColor(Asset.Colors.TextField.Underline.purple.color)
Button(
action: { viewStore.send(.updateDestination(.settings)) },
label: { Text("Settings") }
)
.primaryButtonStyle
.frame(height: 50)
.padding(EdgeInsets(top: 30, leading: 30, bottom: 20, trailing: 30))
Spacer()
HStack { HStack {
VStack { Text("Your UA")
Text("secant v\(viewStore.appVersion)(\(viewStore.appBuild))") .fontWeight(.bold)
Text("sdk v\(viewStore.sdkVersion)") .onTapGesture {
} viewStore.send(.copyUnifiedAddressToPastboard)
Spacer()
Button(
action: { },
label: {
Text("More info")
.foregroundColor(Asset.Colors.Text.moreInfoText.color)
} }
)
Button {
viewStore.send(.updateDestination(.addressDetails))
} label: {
Image(systemName: "info.circle")
.offset(x: -10, y: -10)
.tint(.black)
}
} }
.padding(30)
Text("\(viewStore.unifiedAddress)")
.padding(30)
Spacer()
} }
.onAppear(perform: { viewStore.send(.onAppear) }) .onAppear(perform: { viewStore.send(.onAppear) })
.navigationLinkEmpty(
isActive: viewStore.bindingForSettings,
destination: {
SettingsView(store: store.settingsStore())
}
)
.navigationLinkEmpty( .navigationLinkEmpty(
isActive: viewStore.bindingForAddressDetails, isActive: viewStore.bindingForAddressDetails,
destination: { destination: {
@ -104,14 +74,11 @@ struct ProfileView_Previews: PreviewProvider {
NavigationView { NavigationView {
ProfileView( ProfileView(
store: .init( store: .init(
initialState: .init( initialState: .init(addressDetailsState: .placeholder),
addressDetailsState: .placeholder,
settingsState: .placeholder
),
reducer: ProfileReducer() reducer: ProfileReducer()
) )
) )
} }
.preferredColorScheme(.dark) .preferredColorScheme(.light)
} }
} }

View File

@ -0,0 +1,117 @@
//
// RootDebug.swift
// secant
//
// Created by Lukáš Korba on 02.03.2023.
//
import Foundation
import ComposableArchitecture
import ZcashLightClientKit
/// In this file is a collection of helpers that control all state and action related operations
/// for the `RootReducer` with a connection to the UI navigation.
extension RootReducer {
struct DebugState: Equatable {
var rescanDialog: ConfirmationDialogState<RootReducer.Action>?
}
indirect enum DebugAction: Equatable {
case cancelRescan
case flagUpdated
case fullRescan
case quickRescan
case rescanBlockchain
case rewindDone(String?, RootReducer.Action)
case testCrashReporter // this will crash the app if live.
case updateFlag(FeatureFlag, Bool)
case walletConfigLoaded(WalletConfig)
}
// swiftlint:disable:next cyclomatic_complexity
func debugReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
Reduce { state, action in
switch action {
case .debug(.testCrashReporter):
crashReporter.testCrash()
return .none
case .debug(.rescanBlockchain):
state.debugState.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.debug(.quickRescan))),
.default(TextState("Full rescan"), action: .send(.debug(.fullRescan))),
.cancel(TextState("Cancel"))
]
)
return .none
case .debug(.cancelRescan):
state.debugState.rescanDialog = nil
return .none
case .debug(.quickRescan):
state.destinationState.destination = .home
return .run { send in
do {
try await sdkSynchronizer.rewind(.quick)
await send(.debug(.rewindDone(nil, .debug(.quickRescan))))
} catch {
await send(.debug(.rewindDone(error.localizedDescription, .debug(.quickRescan))))
}
}
case .debug(.fullRescan):
state.destinationState.destination = .home
return .run { send in
do {
try await sdkSynchronizer.rewind(.birthday)
await send(.debug(.rewindDone(nil, .debug(.fullRescan))))
} catch {
await send(.debug(.rewindDone(error.localizedDescription, .debug(.fullRescan))))
}
}
case let .debug(.rewindDone(errorDescription, _)):
if let errorDescription {
// TODO: [#221] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState("Rewind failed"),
message: TextState("Error: \(errorDescription)"),
dismissButton: .default(TextState("Ok"), action: .send(.dismissAlert))
)
}
return .none
case let .debug(.updateFlag(flag, isEnabled)):
return walletConfigProvider.update(flag, !isEnabled)
.receive(on: mainQueue)
.map { _ in return Action.debug(.flagUpdated) }
.eraseToEffect()
.cancellable(id: WalletConfigCancelId.self, cancelInFlight: true)
case .debug(.flagUpdated):
return walletConfigProvider.load()
.receive(on: mainQueue)
.map { Action.debug(.walletConfigLoaded($0)) }
.eraseToEffect()
.cancellable(id: WalletConfigCancelId.self, cancelInFlight: true)
case let .debug(.walletConfigLoaded(walletConfig)):
return EffectTask(value: .updateStateAfterConfigUpdate(walletConfig))
default: return .none
}
}
}
}
// MARK: Placeholders
extension RootReducer.DebugState {
static var placeholder: Self {
.init()
}
}

View File

@ -134,8 +134,8 @@ extension RootReducer {
state.destinationState.alert = nil state.destinationState.alert = nil
return .none return .none
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .updateStateAfterConfigUpdate,
.sandbox, .welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .dismissAlert: .welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .dismissAlert:
return .none return .none
} }

View File

@ -26,12 +26,6 @@ extension RootReducer {
case walletConfigChanged(WalletConfig) case walletConfigChanged(WalletConfig)
} }
enum DebugAction: Equatable {
case updateFlag(FeatureFlag, Bool)
case flagUpdated
case walletConfigLoaded(WalletConfig)
}
// swiftlint:disable:next cyclomatic_complexity function_body_length // swiftlint:disable:next cyclomatic_complexity function_body_length
func initializationReduce() -> Reduce<RootReducer.State, RootReducer.Action> { func initializationReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
Reduce { state, action in Reduce { state, action in
@ -56,8 +50,10 @@ extension RootReducer {
} }
case .initialization(.walletConfigChanged(let walletConfig)): case .initialization(.walletConfigChanged(let walletConfig)):
updateStateAfterConfigUpdate(state: &state, config: walletConfig) return .concatenate(
return EffectTask(value: .initialization(.initialSetups)) EffectTask(value: .updateStateAfterConfigUpdate(walletConfig)),
EffectTask(value: .initialization(.initialSetups))
)
case .initialization(.initialSetups): case .initialization(.initialSetups):
// TODO: [#524] finish all the wallet events according to definition, https://github.com/zcash/secant-ios-wallet/issues/524 // TODO: [#524] finish all the wallet events according to definition, https://github.com/zcash/secant-ios-wallet/issues/524
@ -308,42 +304,25 @@ extension RootReducer {
case .onboarding(.createNewWallet): case .onboarding(.createNewWallet):
return EffectTask(value: .initialization(.createNewWallet)) return EffectTask(value: .initialization(.createNewWallet))
case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome, .binding:
return .none
case .initialization(.configureCrashReporter): case .initialization(.configureCrashReporter):
crashReporter.configure( crashReporter.configure(
!userStoredPreferences.isUserOptedOutOfCrashReporting() !userStoredPreferences.isUserOptedOutOfCrashReporting()
) )
return .none return .none
case .updateStateAfterConfigUpdate(let walletConfig):
state.walletConfig = walletConfig
state.onboardingState.walletConfig = walletConfig
return .none
case .dismissAlert: case .dismissAlert:
state.alert = nil state.alert = nil
return .none return .none
case let .debug(.updateFlag(flag, isEnabled)): case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox,
return walletConfigProvider.update(flag, !isEnabled) .welcome, .binding, .debug:
.receive(on: mainQueue)
.map { _ in return Action.debug(.flagUpdated) }
.eraseToEffect()
.cancellable(id: WalletConfigCancelId.self, cancelInFlight: true)
case .debug(.flagUpdated):
return walletConfigProvider.load()
.receive(on: mainQueue)
.map { Action.debug(.walletConfigLoaded($0)) }
.eraseToEffect()
.cancellable(id: WalletConfigCancelId.self, cancelInFlight: true)
case let .debug(.walletConfigLoaded(walletConfig)):
updateStateAfterConfigUpdate(state: &state, config: walletConfig)
return .none return .none
} }
} }
} }
private func updateStateAfterConfigUpdate(state: inout RootReducer.State, config: WalletConfig) {
state.walletConfig = config
state.onboardingState.walletConfig = config
}
} }

View File

@ -12,6 +12,7 @@ struct RootReducer: ReducerProtocol {
struct State: Equatable { struct State: Equatable {
@BindingState var alert: AlertState<RootReducer.Action>? @BindingState var alert: AlertState<RootReducer.Action>?
var appInitializationState: InitializationState = .uninitialized var appInitializationState: InitializationState = .uninitialized
var debugState: DebugState
var destinationState: DestinationState var destinationState: DestinationState
var homeState: HomeReducer.State var homeState: HomeReducer.State
var onboardingState: OnboardingFlowReducer.State var onboardingState: OnboardingFlowReducer.State
@ -36,6 +37,7 @@ struct RootReducer: ReducerProtocol {
case phraseDisplay(RecoveryPhraseDisplayReducer.Action) case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
case phraseValidation(RecoveryPhraseValidationFlowReducer.Action) case phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
case sandbox(SandboxReducer.Action) case sandbox(SandboxReducer.Action)
case updateStateAfterConfigUpdate(WalletConfig)
case walletConfigLoaded(WalletConfig) case walletConfigLoaded(WalletConfig)
case welcome(WelcomeReducer.Action) case welcome(WelcomeReducer.Action)
} }
@ -83,6 +85,8 @@ struct RootReducer: ReducerProtocol {
initializationReduce() initializationReduce()
destinationReduce() destinationReduce()
debugReduce()
} }
} }
@ -172,6 +176,7 @@ extension RootReducer {
extension RootReducer.State { extension RootReducer.State {
static var placeholder: Self { static var placeholder: Self {
.init( .init(
debugState: .placeholder,
destinationState: .placeholder, destinationState: .placeholder,
homeState: .placeholder, homeState: .placeholder,
onboardingState: .init( onboardingState: .init(

View File

@ -126,6 +126,14 @@ private extension RootView {
viewStore.goToDestination(.welcome) viewStore.goToDestination(.welcome)
} }
Button("Test Crash Reporter") {
viewStore.send(.debug(.testCrashReporter))
}
Button("Rescan Blockchain") {
viewStore.send(.debug(.rescanBlockchain))
}
Button("[Be careful] Nuke Wallet") { Button("[Be careful] Nuke Wallet") {
viewStore.send(.initialization(.nukeWalletRequest)) viewStore.send(.initialization(.nukeWalletRequest))
} }
@ -156,6 +164,10 @@ private extension RootView {
} }
} }
.alert(self.store.scope(state: \.destinationState.alert), dismiss: .destination(.dismissAlert)) .alert(self.store.scope(state: \.destinationState.alert), dismiss: .destination(.dismissAlert))
.confirmationDialog(
store.scope(state: \.debugState.rescanDialog),
dismiss: .debug(.cancelRescan)
)
} }
.navigationBarTitle("Startup") .navigationBarTitle("Startup")
} }

View File

@ -17,7 +17,6 @@ struct SettingsReducer: ReducerProtocol {
@BindingState var isCrashReportingOn: Bool @BindingState var isCrashReportingOn: Bool
var isSharingLogs = false var isSharingLogs = false
var phraseDisplayState: RecoveryPhraseDisplayReducer.State var phraseDisplayState: RecoveryPhraseDisplayReducer.State
var rescanDialog: ConfirmationDialogState<SettingsReducer.Action>?
var supportData: SupportData? var supportData: SupportData?
var tempSDKDir: URL { var tempSDKDir: URL {
@ -43,20 +42,15 @@ struct SettingsReducer: ReducerProtocol {
case backupWallet case backupWallet
case backupWalletAccessRequest case backupWalletAccessRequest
case binding(BindingAction<SettingsReducer.State>) case binding(BindingAction<SettingsReducer.State>)
case cancelRescan
case dismissAlert case dismissAlert
case exportLogs case exportLogs
case fullRescan
case logsExported case logsExported
case logsExportFailed(String) case logsExportFailed(String)
case logsShareFinished case logsShareFinished
case onAppear case onAppear
case phraseDisplay(RecoveryPhraseDisplayReducer.Action) case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
case quickRescan
case rescanBlockchain
case sendSupportMail case sendSupportMail
case sendSupportMailFinished case sendSupportMailFinished
case testCrashReporter // this will crash the app if live.
case updateDestination(SettingsReducer.State.Destination?) case updateDestination(SettingsReducer.State.Destination?)
} }
@ -109,10 +103,6 @@ struct SettingsReducer: ReducerProtocol {
await userStoredPreferences.setIsUserOptedOutOfCrashReporting(state.isCrashReportingOn) await userStoredPreferences.setIsUserOptedOutOfCrashReporting(state.isCrashReportingOn)
} }
case .cancelRescan, .quickRescan, .fullRescan:
state.rescanDialog = nil
return .none
case .dismissAlert: case .dismissAlert:
state.alert = nil state.alert = nil
return .none return .none
@ -146,18 +136,6 @@ struct SettingsReducer: ReducerProtocol {
state.isSharingLogs = false state.isSharingLogs = false
return .none return .none
case .rescanBlockchain:
state.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
return .none
case .phraseDisplay: case .phraseDisplay:
state.destination = nil state.destination = nil
return .none return .none
@ -166,10 +144,6 @@ struct SettingsReducer: ReducerProtocol {
state.destination = destination state.destination = destination
return .none return .none
case .testCrashReporter:
crashReporter.testCrash()
return .none
case .binding: case .binding:
return .none return .none

View File

@ -18,13 +18,6 @@ struct SettingsView: View {
.activeButtonStyle .activeButtonStyle
.frame(height: 50) .frame(height: 50)
Button(
action: { viewStore.send(.rescanBlockchain) },
label: { Text("Rescan Blockchain") }
)
.primaryButtonStyle
.frame(height: 50)
Button( Button(
action: { viewStore.send(.exportLogs) }, action: { viewStore.send(.exportLogs) },
label: { label: {
@ -42,13 +35,6 @@ struct SettingsView: View {
.frame(height: 50) .frame(height: 50)
.disabled(viewStore.exportLogsDisabled) .disabled(viewStore.exportLogsDisabled)
Button(
action: { viewStore.send(.testCrashReporter) },
label: { Text("Test Crash Reporter") }
)
.primaryButtonStyle
.frame(height: 50)
Button( Button(
action: { viewStore.send(.sendSupportMail) }, action: { viewStore.send(.sendSupportMail) },
label: { Text("Send us feedback!") } label: { Text("Send us feedback!") }
@ -61,10 +47,6 @@ struct SettingsView: View {
.padding(.horizontal, 30) .padding(.horizontal, 30)
.navigationTitle("Settings") .navigationTitle("Settings")
.applyScreenBackground() .applyScreenBackground()
.confirmationDialog(
store.scope(state: \.rescanDialog),
dismiss: .cancelRescan
)
.navigationLinkEmpty( .navigationLinkEmpty(
isActive: viewStore.bindingForBackupPhrase, isActive: viewStore.bindingForBackupPhrase,
destination: { destination: {

View File

@ -87,54 +87,4 @@ class HomeTests: XCTestCase {
// the .onDisappear action cancles the observer of the synchronizer status change. // the .onDisappear action cancles the observer of the synchronizer status change.
store.send(.onDisappear) store.send(.onDisappear)
} }
@MainActor func testQuickRescan_ResetToHomeScreen() async throws {
let homeState = HomeReducer.State(
balanceBreakdownState: .placeholder,
destination: .profile,
profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: Balance.zero,
synchronizerStatusSnapshot: .default,
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
initialState: homeState,
reducer: HomeReducer()
)
await store.send(.profile(.settings(.quickRescan))) { state in
state.destination = nil
}
await store.receive(.rewindDone(nil, .quickRescan))
}
@MainActor func testFullRescan_ResetToHomeScreen() async throws {
let homeState = HomeReducer.State(
balanceBreakdownState: .placeholder,
destination: .profile,
profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: Balance.zero,
synchronizerStatusSnapshot: .default,
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
initialState: homeState,
reducer: HomeReducer()
)
await store.send(.profile(.settings(.fullRescan))) { state in
state.destination = nil
}
await store.receive(.rewindDone(nil, .fullRescan))
}
} }

View File

@ -11,6 +11,9 @@ import ComposableArchitecture
import ZcashLightClientKit import ZcashLightClientKit
class ProfileTests: XCTestCase { class ProfileTests: XCTestCase {
// swiftlint:disable line_length
let uAddressEncoding = "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h"
@MainActor func testSynchronizerStateChanged_AnyButSynced() async throws { @MainActor func testSynchronizerStateChanged_AnyButSynced() async throws {
let store = TestStore( let store = TestStore(
initialState: .placeholder, initialState: .placeholder,
@ -20,9 +23,8 @@ class ProfileTests: XCTestCase {
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
} }
// swiftlint:disable line_length
let uAddress = try UnifiedAddress( let uAddress = try UnifiedAddress(
encoding: "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h", encoding: uAddressEncoding,
network: .testnet network: .testnet
) )
@ -33,4 +35,26 @@ class ProfileTests: XCTestCase {
state.sdkVersion = "0.18.1-beta" state.sdkVersion = "0.18.1-beta"
} }
} }
func testCopyUnifiedAddressToPasteboard() throws {
let testPasteboard = PasteboardClient.testPasteboard
let uAddress = try UnifiedAddress(encoding: uAddressEncoding, network: .testnet)
let store = TestStore(
initialState: ProfileReducer.State(
addressDetailsState: AddressDetailsReducer.State(uAddress: uAddress)
),
reducer: ProfileReducer()
) {
$0.pasteboard = testPasteboard
}
store.send(.copyUnifiedAddressToPastboard)
XCTAssertEqual(
testPasteboard.getString()?.data,
uAddress.stringEncoded,
"AddressDetails: `testCopyUnifiedAddressToPasteboard` is expected to match the input `\(uAddress.stringEncoded)`"
)
}
} }

View File

@ -161,6 +161,7 @@ class RecoveryPhraseValidationFlowFeatureFlagTests: XCTestCase {
) )
let appState = RootReducer.State( let appState = RootReducer.State(
debugState: .placeholder,
destinationState: .placeholder, destinationState: .placeholder,
homeState: .placeholder, homeState: .placeholder,
onboardingState: .init( onboardingState: .init(

View File

@ -68,6 +68,7 @@ class AppInitializationTests: XCTestCase {
let walletConfig = WalletConfig(flags: defaultRawFlags) let walletConfig = WalletConfig(flags: defaultRawFlags)
let appState = RootReducer.State( let appState = RootReducer.State(
debugState: .placeholder,
destinationState: .placeholder, destinationState: .placeholder,
homeState: .placeholder, homeState: .placeholder,
onboardingState: .init( onboardingState: .init(

View File

@ -0,0 +1,107 @@
//
// DebugTests.swift
// secantTests
//
// Created by Lukáš Korba on 02.03.2023.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
@MainActor
class DebugTests: XCTestCase {
func testRescanBlockchain() async throws {
let store = TestStore(
initialState: .placeholder,
reducer: RootReducer()
)
await store.send(.debug(.rescanBlockchain)) { state in
state.debugState.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.debug(.quickRescan))),
.default(TextState("Full rescan"), action: .send(.debug(.fullRescan))),
.cancel(TextState("Cancel"))
]
)
}
}
func testRescanBlockchain_Cancelling() async throws {
var mockState = RootReducer.State.placeholder
mockState.debugState.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.debug(.quickRescan))),
.default(TextState("Full rescan"), action: .send(.debug(.fullRescan))),
.cancel(TextState("Cancel"))
]
)
let store = TestStore(
initialState: mockState,
reducer: RootReducer()
)
await store.send(.debug(.cancelRescan)) { state in
state.debugState.rescanDialog = nil
}
}
func testRescanBlockchain_QuickRescanClearance() async throws {
var mockState = RootReducer.State.placeholder
mockState.debugState.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.debug(.quickRescan))),
.default(TextState("Full rescan"), action: .send(.debug(.fullRescan))),
.cancel(TextState("Cancel"))
]
)
let store = TestStore(
initialState: mockState,
reducer: RootReducer()
)
await store.send(.debug(.quickRescan)) { state in
state.destinationState.internalDestination = .home
state.destinationState.previousDestination = .welcome
}
await store.receive(.debug(.rewindDone(nil, .debug(.quickRescan))))
}
func testRescanBlockchain_FullRescanClearance() async throws {
var mockState = RootReducer.State.placeholder
mockState.debugState.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.debug(.quickRescan))),
.default(TextState("Full rescan"), action: .send(.debug(.fullRescan))),
.cancel(TextState("Cancel"))
]
)
let store = TestStore(
initialState: mockState,
reducer: RootReducer()
)
await store.send(.debug(.fullRescan)) { state in
state.destinationState.internalDestination = .home
state.destinationState.previousDestination = .welcome
}
await store.receive(.debug(.rewindDone(nil, .debug(.fullRescan))))
}
}

View File

@ -80,112 +80,12 @@ class SettingsTests: XCTestCase {
await store.finish() await store.finish()
} }
func testRescanBlockchain() async throws {
let store = TestStore(
initialState: .placeholder,
reducer: SettingsReducer()
)
await store.send(.rescanBlockchain) { state in
state.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
}
}
func testRescanBlockchain_Cancelling() async throws {
let store = TestStore(
initialState: SettingsReducer.State(
destination: nil,
isCrashReportingOn: false,
phraseDisplayState: .init(),
rescanDialog: .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
),
reducer: SettingsReducer()
)
await store.send(.cancelRescan) { state in
state.rescanDialog = nil
}
}
func testRescanBlockchain_QuickRescanClearance() async throws {
let store = TestStore(
initialState: SettingsReducer.State(
destination: nil,
isCrashReportingOn: false,
phraseDisplayState: .init(),
rescanDialog: .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
),
reducer: SettingsReducer()
)
await store.send(.quickRescan) { state in
state.rescanDialog = nil
}
}
func testRescanBlockchain_FullRescanClearance() async throws {
let store = TestStore(
initialState: SettingsReducer.State(
destination: nil,
isCrashReportingOn: false,
phraseDisplayState: .init(),
rescanDialog: .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
),
reducer: SettingsReducer()
)
await store.send(.fullRescan) { state in
state.rescanDialog = nil
}
}
func testExportLogs_ButtonDisableShareEnable() async throws { func testExportLogs_ButtonDisableShareEnable() async throws {
let store = TestStore( let store = TestStore(
initialState: SettingsReducer.State( initialState: SettingsReducer.State(
destination: nil, destination: nil,
isCrashReportingOn: false, isCrashReportingOn: false,
phraseDisplayState: .init(), phraseDisplayState: .init()
rescanDialog: .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
), ),
reducer: SettingsReducer() reducer: SettingsReducer()
) )
@ -208,16 +108,7 @@ class SettingsTests: XCTestCase {
destination: nil, destination: nil,
isCrashReportingOn: false, isCrashReportingOn: false,
isSharingLogs: true, isSharingLogs: true,
phraseDisplayState: .init(), phraseDisplayState: .init()
rescanDialog: .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
), ),
reducer: SettingsReducer() reducer: SettingsReducer()
) )

View File

@ -39,9 +39,9 @@ class HomeSnapshotTests: XCTestCase {
initialState: .init( initialState: .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: balance.redacted, shieldedBalance: balance.redacted,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)) walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents))

View File

@ -54,9 +54,9 @@ class WalletEventsSnapshotTests: XCTestCase {
initialState: .init( initialState: .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: balance.redacted, shieldedBalance: balance.redacted,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])) walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
@ -106,9 +106,9 @@ class WalletEventsSnapshotTests: XCTestCase {
initialState: .init( initialState: .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: balance.redacted, shieldedBalance: balance.redacted,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])) walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
@ -158,9 +158,9 @@ class WalletEventsSnapshotTests: XCTestCase {
initialState: .init( initialState: .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: balance.redacted, shieldedBalance: balance.redacted,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])) walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
@ -216,9 +216,9 @@ class WalletEventsSnapshotTests: XCTestCase {
initialState: .init( initialState: .init(
balanceBreakdownState: .placeholder, balanceBreakdownState: .placeholder,
profileState: .placeholder, profileState: .placeholder,
requestState: .placeholder,
scanState: .placeholder, scanState: .placeholder,
sendState: .placeholder, sendState: .placeholder,
settingsState: .placeholder,
shieldedBalance: balance.redacted, shieldedBalance: balance.redacted,
synchronizerStatusSnapshot: .default, synchronizerStatusSnapshot: .default,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])) walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))

View File

@ -127,8 +127,10 @@ class WalletConfigProviderTests: XCTestCase {
defaultRawFlags[.onboardingFlow] = true defaultRawFlags[.onboardingFlow] = true
let flags = WalletConfig(flags: defaultRawFlags) let flags = WalletConfig(flags: defaultRawFlags)
store.send(.debug(.walletConfigLoaded(flags)))
// The new flag's value has to be propagated to all `walletConfig` instances // The new flag's value has to be propagated to all `walletConfig` instances
store.send(.debug(.walletConfigLoaded(flags))) { state in store.receive(.updateStateAfterConfigUpdate(flags)) { state in
state.walletConfig = flags state.walletConfig = flags
state.onboardingState.walletConfig = flags state.onboardingState.walletConfig = flags
} }