diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 9e9cc89..0b6951b 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -144,6 +144,7 @@ 9E6713F8289BC58C00A6796F /* BalanceBreakdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */; }; 9E6713FA289BE0E100A6796F /* ClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */; }; 9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; }; + 9E6EF2CB291287BB00CA007B /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6EF2CA291287BB00CA007B /* FeedbackGenerator.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 */; }; @@ -389,6 +390,7 @@ 9E6713F6289BC58C00A6796F /* BalanceBreakdownStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownStore.swift; sourceTree = ""; }; 9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearBackgroundView.swift; sourceTree = ""; }; 9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = ""; }; + 9E6EF2CA291287BB00CA007B /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; 9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSnapshotTests.swift; sourceTree = ""; }; 9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleLineTextField.swift; sourceTree = ""; }; 9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldStore.swift; sourceTree = ""; }; @@ -1128,6 +1130,7 @@ 9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */, 9EF1082A29114B93003D8097 /* Pasteboard.swift */, 9EF1082C29114BCD003D8097 /* NewRecoveryPhrase.swift */, + 9E6EF2CA291287BB00CA007B /* FeedbackGenerator.swift */, ); path = Dependencies; sourceTree = ""; @@ -1671,6 +1674,7 @@ 9E7CB6292875AC2D00A02233 /* AppVersionHandler.swift in Sources */, 0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */, 9EAB467A2861EA6A002904A0 /* TransactionRowView.swift in Sources */, + 9E6EF2CB291287BB00CA007B /* FeedbackGenerator.swift in Sources */, 0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */, 9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */, 0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */, diff --git a/secant/Dependencies/FeedbackGenerator.swift b/secant/Dependencies/FeedbackGenerator.swift new file mode 100644 index 0000000..dbeae07 --- /dev/null +++ b/secant/Dependencies/FeedbackGenerator.swift @@ -0,0 +1,20 @@ +// +// FeedbackGenerator.swift +// secant-testnet +// +// Created by Lukáš Korba on 02.11.2022. +// + +import ComposableArchitecture + +private enum FeedbackGenerator: DependencyKey { + static let liveValue = WrappedFeedbackGenerator.haptic + static let testValue = WrappedFeedbackGenerator.silent +} + +extension DependencyValues { + var feedbackGenerator: WrappedFeedbackGenerator { + get { self[FeedbackGenerator.self] } + set { self[FeedbackGenerator.self] = newValue } + } +} diff --git a/secant/Dependencies/RecoveryPhraseRandomizer.swift b/secant/Dependencies/RecoveryPhraseRandomizer.swift index a60e825..741664c 100644 --- a/secant/Dependencies/RecoveryPhraseRandomizer.swift +++ b/secant/Dependencies/RecoveryPhraseRandomizer.swift @@ -8,11 +8,11 @@ import Foundation struct RecoveryPhraseRandomizer { - func random(phrase: RecoveryPhrase) -> RecoveryPhraseValidationFlowState { + func random(phrase: RecoveryPhrase) -> RecoveryPhraseValidationFlow.State { let missingIndices = randomIndices() let missingWordChipKind = phrase.words(fromMissingIndices: missingIndices).shuffled() - return RecoveryPhraseValidationFlowState( + return RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: missingWordChipKind, @@ -21,8 +21,21 @@ struct RecoveryPhraseRandomizer { } func randomIndices() -> [Int] { - return (0.. -typealias AnyAppReducer = AnyReducer typealias AppStore = Store typealias AppViewStore = ViewStore +typealias AnyRecoveryPhraseDisplayReducer = AnyReducer +typealias AnyRecoveryPhraseValidationFlowReducer = AnyReducer + // MARK: - State struct AppState: Equatable { @@ -23,7 +25,7 @@ struct AppState: Equatable { var appInitializationState: InitializationState = .uninitialized var homeState: HomeState var onboardingState: OnboardingFlowState - var phraseValidationState: RecoveryPhraseValidationFlowState + var phraseValidationState: RecoveryPhraseValidationFlow.State var phraseDisplayState: RecoveryPhraseDisplay.State var prevRoute: Route? var internalRoute: Route = .welcome @@ -55,7 +57,7 @@ enum AppAction: Equatable { case nukeWallet case onboarding(OnboardingFlowAction) case phraseDisplay(RecoveryPhraseDisplay.Action) - case phraseValidation(RecoveryPhraseValidationFlowAction) + case phraseValidation(RecoveryPhraseValidationFlow.Action) case respondToWalletInitializationState(InitializationState) case sandbox(SandboxAction) case updateRoute(AppState.Route) @@ -390,20 +392,16 @@ extension AppReducer { } ) - private static let phraseValidationReducer: AppReducer = RecoveryPhraseValidationFlowReducer.default.pullback( + private static let phraseValidationReducer: AppReducer = AnyRecoveryPhraseValidationFlowReducer { _ in + RecoveryPhraseValidationFlow() + } + .pullback( state: \AppState.phraseValidationState, action: /AppAction.phraseValidation, - environment: { environment in - RecoveryPhraseValidationFlowEnvironment( - scheduler: environment.scheduler, - pasteboard: .test, - feedbackGenerator: .silent, - recoveryPhraseRandomizer: environment.recoveryPhraseRandomizer - ) - } + environment: { $0 } ) - - private static let phraseDisplayReducer: AppReducer = AnyAppReducer { _ in + + private static let phraseDisplayReducer: AppReducer = AnyRecoveryPhraseDisplayReducer { _ in RecoveryPhraseDisplay() } .pullback( diff --git a/secant/Features/RecoveryPhraseValidationFlow/RecoveryPhraseValidationFlowStore.swift b/secant/Features/RecoveryPhraseValidationFlow/RecoveryPhraseValidationFlowStore.swift index 1d66ee9..f471da4 100644 --- a/secant/Features/RecoveryPhraseValidationFlow/RecoveryPhraseValidationFlowStore.swift +++ b/secant/Features/RecoveryPhraseValidationFlow/RecoveryPhraseValidationFlowStore.swift @@ -9,43 +9,114 @@ import Foundation import ComposableArchitecture import SwiftUI -typealias RecoveryPhraseValidationFlowReducer = Reducer< - RecoveryPhraseValidationFlowState, - RecoveryPhraseValidationFlowAction, - RecoveryPhraseValidationFlowEnvironment -> -typealias RecoveryPhraseValidationFlowStore = Store -typealias RecoveryPhraseValidationFlowViewStore = ViewStore +typealias RecoveryPhraseValidationFlowStore = Store +typealias RecoveryPhraseValidationFlowViewStore = ViewStore -// MARK: - State +struct RecoveryPhraseValidationFlow: ReducerProtocol { + struct State: Equatable { + enum Route: Equatable, CaseIterable { + case validation + case success + case failure + } -struct RecoveryPhraseValidationFlowState: Equatable { - enum Route: Equatable, CaseIterable { - case validation - case success - case failure + static let wordGroupSize = 6 + static let phraseChunks = 4 + + var phrase: RecoveryPhrase + var missingIndices: [Int] + var missingWordChips: [PhraseChip.Kind] + var validationWords: [ValidationWord] + var route: Route? + + var isComplete: Bool { + !validationWords.isEmpty && validationWords.count == missingIndices.count + } + + var isValid: Bool { + guard let resultingPhrase = self.resultingPhrase else { return false } + return resultingPhrase == phrase.words + } } - - static let wordGroupSize = 6 - static let phraseChunks = 4 - - var phrase: RecoveryPhrase - var missingIndices: [Int] - var missingWordChips: [PhraseChip.Kind] - var validationWords: [ValidationWord] - var route: Route? - var isComplete: Bool { - !validationWords.isEmpty && validationWords.count == missingIndices.count - } + @Dependency(\.randomPhrase) var randomPhrase + @Dependency(\.mainQueue) var mainQueue + @Dependency(\.pasteboard) var pasteboard + @Dependency(\.feedbackGenerator) var feedbackGenerator - var isValid: Bool { - guard let resultingPhrase = self.resultingPhrase else { return false } - return resultingPhrase == phrase.words + enum Action: Equatable { + case updateRoute(RecoveryPhraseValidationFlow.State.Route?) + case reset + case move(wordChip: PhraseChip.Kind, intoGroup: Int) + case succeed + case fail + case failureFeedback + case proceedToHome + case displayBackedUpPhrase + } + + // swiftlint:disable:next cyclomatic_complexity + func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask { + switch action { + case .reset: + state = randomPhrase.random(state.phrase) + state.route = .validation + // FIXME [#186]: Resetting causes route to be nil = preamble screen, hence setting the .validation. The transition back is not animated + // though + + case let .move(wordChip, group): + guard + case let PhraseChip.Kind.unassigned(word, _) = wordChip, + let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip) + else { return .none } + + state.missingWordChips[missingChipIndex] = .empty + state.validationWords.append(ValidationWord(groupIndex: group, word: word)) + + if state.isComplete { + let value: RecoveryPhraseValidationFlow.Action = state.isValid ? .succeed : .fail + let effect = Effect(value: value) + .delay(for: 1, scheduler: mainQueue) + .eraseToEffect() + + if value == .succeed { + return effect + } else { + return .concatenate( + Effect(value: .failureFeedback), + effect + ) + } + } + return .none + + case .succeed: + state.route = .success + + case .fail: + state.route = .failure + + case .failureFeedback: + feedbackGenerator.generateErrorFeedback() + + case .updateRoute(let route): + guard let route = route else { + state = randomPhrase.random(state.phrase) + return .none + } + state.route = route + + case .proceedToHome: + break + + case .displayBackedUpPhrase: + break + } + return .none } } -extension RecoveryPhraseValidationFlowState { +extension RecoveryPhraseValidationFlow.State { /// Given an array of RecoveryPhraseStepCompletion, missing indices, original phrase and the number of groups it was split into, /// assembly the resulting phrase. This comes up with the "proposed solution" for the recovery phrase validation challenge. /// - returns:an array of String containing the recovery phrase words ordered by the original phrase order, or `nil` @@ -95,111 +166,10 @@ extension RecoveryPhrase.Group { } } -// MARK: - Action - -enum RecoveryPhraseValidationFlowAction: Equatable { - case updateRoute(RecoveryPhraseValidationFlowState.Route?) - case reset - case move(wordChip: PhraseChip.Kind, intoGroup: Int) - case succeed - case fail - case failureFeedback - case proceedToHome - case displayBackedUpPhrase -} - -// MARK: - Environment - -struct RecoveryPhraseValidationFlowEnvironment { - let scheduler: AnySchedulerOf - let pasteboard: WrappedPasteboard - let feedbackGenerator: WrappedFeedbackGenerator - let recoveryPhraseRandomizer: WrappedRecoveryPhraseRandomizer -} - -extension RecoveryPhraseValidationFlowEnvironment { - static let demo = Self( - scheduler: DispatchQueue.main.eraseToAnyScheduler(), - pasteboard: .test, - feedbackGenerator: .silent, - recoveryPhraseRandomizer: .live - ) - - static let live = Self( - scheduler: DispatchQueue.main.eraseToAnyScheduler(), - pasteboard: .live, - feedbackGenerator: .haptic, - recoveryPhraseRandomizer: .live - ) -} - -// MARK: - Reducer - -extension RecoveryPhraseValidationFlowReducer { - static let `default` = RecoveryPhraseValidationFlowReducer { state, action, environment in - switch action { - case .reset: - state = environment.recoveryPhraseRandomizer.random(state.phrase) - state.route = .validation - // FIXME [#186]: Resetting causes route to be nil = preamble screen, hence setting the .validation. The transition back is not animated - // though - - case let .move(wordChip, group): - guard - case let PhraseChip.Kind.unassigned(word, color) = wordChip, - let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip) - else { return .none } - - state.missingWordChips[missingChipIndex] = .empty - state.validationWords.append(ValidationWord(groupIndex: group, word: word)) - - if state.isComplete { - let value: RecoveryPhraseValidationFlowAction = state.isValid ? .succeed : .fail - let effect = Effect(value: value) - .delay(for: 1, scheduler: environment.scheduler) - .eraseToEffect() - - if value == .succeed { - return effect - } else { - return .concatenate( - Effect(value: .failureFeedback), - effect - ) - } - } - return .none - - case .succeed: - state.route = .success - - case .fail: - state.route = .failure - - case .failureFeedback: - environment.feedbackGenerator.generateErrorFeedback() - - case .updateRoute(let route): - guard let route = route else { - state = environment.recoveryPhraseRandomizer.random(state.phrase) - return .none - } - state.route = route - - case .proceedToHome: - break - - case .displayBackedUpPhrase: - break - } - return .none - } -} - // MARK: - ViewStore extension RecoveryPhraseValidationFlowViewStore { - func bindingForRoute(_ route: RecoveryPhraseValidationFlowState.Route) -> Binding { + func bindingForRoute(_ route: RecoveryPhraseValidationFlow.State.Route) -> Binding { self.binding( get: { $0.route == route }, send: { isActive in @@ -240,8 +210,8 @@ extension RecoveryPhraseValidationFlowViewStore { // MARK: - Placeholders -extension RecoveryPhraseValidationFlowState { - static let placeholder = RecoveryPhraseValidationFlowState( +extension RecoveryPhraseValidationFlow.State { + static let placeholder = RecoveryPhraseValidationFlow.State( phrase: .placeholder, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -254,7 +224,7 @@ extension RecoveryPhraseValidationFlowState { route: nil ) - static let placeholderStep1 = RecoveryPhraseValidationFlowState( + static let placeholderStep1 = RecoveryPhraseValidationFlow.State( phrase: .placeholder, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -269,7 +239,7 @@ extension RecoveryPhraseValidationFlowState { route: nil ) - static let placeholderStep2 = RecoveryPhraseValidationFlowState( + static let placeholderStep2 = RecoveryPhraseValidationFlow.State( phrase: .placeholder, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -285,7 +255,7 @@ extension RecoveryPhraseValidationFlowState { route: nil ) - static let placeholderStep3 = RecoveryPhraseValidationFlowState( + static let placeholderStep3 = RecoveryPhraseValidationFlow.State( phrase: .placeholder, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -302,7 +272,7 @@ extension RecoveryPhraseValidationFlowState { route: nil ) - static let placeholderStep4 = RecoveryPhraseValidationFlowState( + static let placeholderStep4 = RecoveryPhraseValidationFlow.State( phrase: .placeholder, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -324,31 +294,26 @@ extension RecoveryPhraseValidationFlowState { extension RecoveryPhraseValidationFlowStore { static let demo = Store( initialState: .placeholder, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) - + static let demoStep1 = Store( initialState: .placeholderStep1, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) static let demoStep2 = Store( initialState: .placeholderStep1, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) static let demoStep3 = Store( initialState: .placeholderStep3, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) static let demoStep4 = Store( initialState: .placeholderStep4, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) } diff --git a/secant/Features/RecoveryPhraseValidationFlow/Views/RecoveryPhraseBackupView.swift b/secant/Features/RecoveryPhraseValidationFlow/Views/RecoveryPhraseBackupView.swift index 195da58..1ff2ca6 100644 --- a/secant/Features/RecoveryPhraseValidationFlow/Views/RecoveryPhraseBackupView.swift +++ b/secant/Features/RecoveryPhraseValidationFlow/Views/RecoveryPhraseBackupView.swift @@ -83,7 +83,7 @@ private extension RecoveryPhraseBackupView { .padding(.horizontal, 30) } - @ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlowState) -> some View { + @ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlow.State) -> some View { if state.isValid { Text("recoveryPhraseBackupValidation.successResult") .bodyText() @@ -94,7 +94,7 @@ private extension RecoveryPhraseBackupView { } } -private extension RecoveryPhraseValidationFlowState { +private extension RecoveryPhraseValidationFlow.State { @ViewBuilder func missingWordGrid() -> some View { let columns = Array( repeating: GridItem(.flexible(minimum: 100, maximum: 120), spacing: 20), @@ -116,7 +116,7 @@ private extension RecoveryPhraseValidationFlowState { } } -extension RecoveryPhraseValidationFlowState { +extension RecoveryPhraseValidationFlow.State { func wordsChips( for groupIndex: Int, groupSize: Int, @@ -140,14 +140,14 @@ extension RecoveryPhraseValidationFlowState { private extension WordChipGrid { init( - state: RecoveryPhraseValidationFlowState, + state: RecoveryPhraseValidationFlow.State, groupIndex: Int, wordGroup: RecoveryPhrase.Group, misingIndex: Int ) { let chips = state.wordsChips( for: groupIndex, - groupSize: RecoveryPhraseValidationFlowState.wordGroupSize, + groupSize: RecoveryPhraseValidationFlow.State.wordGroupSize, from: wordGroup ) @@ -155,7 +155,7 @@ private extension WordChipGrid { } } -private extension RecoveryPhraseValidationFlowState { +private extension RecoveryPhraseValidationFlow.State { var coloredChipColor: Color { if self.isComplete { return isValid ? Asset.Colors.Buttons.activeButton.color : Asset.Colors.BackgroundColors.red.color diff --git a/secant/Models/DropDelegate.swift b/secant/Models/DropDelegate.swift index 08c8ac7..b280de6 100644 --- a/secant/Models/DropDelegate.swift +++ b/secant/Models/DropDelegate.swift @@ -36,7 +36,7 @@ struct WordChipDropDelegate: DropDelegate { } } -extension RecoveryPhraseValidationFlowState { +extension RecoveryPhraseValidationFlow.State { func groupCompleted(index: Int) -> Bool { validationWords.first(where: { $0.groupIndex == index }) != nil } diff --git a/secant/Wrappers/WrappedRecoveryPhraseRandomizer.swift b/secant/Wrappers/WrappedRecoveryPhraseRandomizer.swift index 2cc84e9..f7887ff 100644 --- a/secant/Wrappers/WrappedRecoveryPhraseRandomizer.swift +++ b/secant/Wrappers/WrappedRecoveryPhraseRandomizer.swift @@ -8,7 +8,7 @@ import Foundation struct WrappedRecoveryPhraseRandomizer { - let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowState + let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlow.State } extension WrappedRecoveryPhraseRandomizer { diff --git a/secantTests/AppTests/AppInitializationTests.swift b/secantTests/AppTests/AppInitializationTests.swift index e97e0b3..7565306 100644 --- a/secantTests/AppTests/AppInitializationTests.swift +++ b/secantTests/AppTests/AppInitializationTests.swift @@ -33,7 +33,7 @@ class AppInitializationTests: XCTestCase { let recoveryPhrase = RecoveryPhrase(words: try WrappedMnemonic.mock.randomMnemonicWords()) - let phraseValidationState = RecoveryPhraseValidationFlowState( + let phraseValidationState = RecoveryPhraseValidationFlow.State( phrase: recoveryPhrase, missingIndices: [2, 0, 3, 5], missingWordChips: [ @@ -67,7 +67,7 @@ class AppInitializationTests: XCTestCase { ) ] - return RecoveryPhraseValidationFlowState( + return RecoveryPhraseValidationFlow.State( phrase: recoveryPhrase, missingIndices: missingIndices, missingWordChips: missingWordChipKind, diff --git a/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift b/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift index cb71144..9c8a1cb 100644 --- a/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift +++ b/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift @@ -12,13 +12,6 @@ import ComposableArchitecture class RecoveryPhraseValidationTests: XCTestCase { static let testScheduler = DispatchQueue.test - let testEnvironment = RecoveryPhraseValidationFlowEnvironment( - scheduler: testScheduler.eraseToAnyScheduler(), - pasteboard: .test, - feedbackGenerator: .silent, - recoveryPhraseRandomizer: .live - ) - func testPickWordsFromMissingIndices() throws { let words = [ "bring", "salute", "thank", @@ -66,14 +59,17 @@ class RecoveryPhraseValidationTests: XCTestCase { let missingWordChips: [PhraseChip.Kind] = ["salute", "boil", "cancel", "pizza"].map({ PhraseChip.Kind.unassigned(word: $0) }) - let initialStep = RecoveryPhraseValidationFlowState( + let initialStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: missingWordChips, validationWords: [] ) - let store = TestStore(initialState: initialStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: initialStep, + reducer: RecoveryPhraseValidationFlow() + ) let expectedMissingChips = [ PhraseChip.Kind.empty, @@ -113,13 +109,16 @@ class RecoveryPhraseValidationTests: XCTestCase { let missingWordChips = ["salute", "boil", "cancel", "pizza"].map({ PhraseChip.Kind.unassigned(word: $0) }) - let initialStep = RecoveryPhraseValidationFlowState.initial( + let initialStep = RecoveryPhraseValidationFlow.State.initial( phrase: phrase, missingIndices: missingIndices, missingWordsChips: missingWordChips ) - let store = TestStore(initialState: initialStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: initialStep, + reducer: RecoveryPhraseValidationFlow() + ) let expectedMissingChips = [ PhraseChip.Kind.unassigned(word: "salute"), @@ -157,7 +156,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -169,7 +168,10 @@ class RecoveryPhraseValidationTests: XCTestCase { validationWords: [ValidationWord(groupIndex: 0, word: "salute")] ) - let store = TestStore(initialState: currentStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: currentStep, + reducer: RecoveryPhraseValidationFlow() + ) let expectedMissingWordChips = [ PhraseChip.Kind.empty, @@ -210,7 +212,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -225,7 +227,10 @@ class RecoveryPhraseValidationTests: XCTestCase { ] ) - let store = TestStore(initialState: currentStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: currentStep, + reducer: RecoveryPhraseValidationFlow() + ) let expectedMissingWordChips = [ PhraseChip.Kind.empty, @@ -267,7 +272,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -283,7 +288,11 @@ class RecoveryPhraseValidationTests: XCTestCase { ] ) - let store = TestStore(initialState: currentStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: currentStep, + reducer: RecoveryPhraseValidationFlow() + .dependency(\.mainQueue, RecoveryPhraseValidationTests.testScheduler.eraseToAnyScheduler()) + ) let expectedMissingWordChips = [ PhraseChip.Kind.empty, @@ -334,7 +343,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -350,7 +359,11 @@ class RecoveryPhraseValidationTests: XCTestCase { ] ) - let store = TestStore(initialState: currentStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment) + let store = TestStore( + initialState: currentStep, + reducer: RecoveryPhraseValidationFlow() + .dependency(\.mainQueue, RecoveryPhraseValidationTests.testScheduler.eraseToAnyScheduler()) + ) let expectedMissingWordChips = [ PhraseChip.Kind.empty, @@ -402,7 +415,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -454,7 +467,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -507,7 +520,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -545,7 +558,7 @@ class RecoveryPhraseValidationTests: XCTestCase { let phrase = RecoveryPhrase(words: words) - let currentStep = RecoveryPhraseValidationFlowState( + let currentStep = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: [ @@ -591,7 +604,7 @@ class RecoveryPhraseValidationTests: XCTestCase { ValidationWord(groupIndex: 3, word: "pizza") ] - let result = RecoveryPhraseValidationFlowState( + let result = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: phrase.words(fromMissingIndices: missingIndices), @@ -630,7 +643,7 @@ class RecoveryPhraseValidationTests: XCTestCase { ValidationWord(groupIndex: 2, word: "pizza") ] - let result = RecoveryPhraseValidationFlowState( + let result = RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: phrase.words(fromMissingIndices: missingIndices), @@ -644,13 +657,13 @@ class RecoveryPhraseValidationTests: XCTestCase { } } -extension RecoveryPhraseValidationFlowState { +extension RecoveryPhraseValidationFlow.State { static func initial( phrase: RecoveryPhrase, missingIndices: [Int], missingWordsChips: [PhraseChip.Kind] ) -> Self { - RecoveryPhraseValidationFlowState( + RecoveryPhraseValidationFlow.State( phrase: phrase, missingIndices: missingIndices, missingWordChips: missingWordsChips, diff --git a/secantTests/SnapshotTests/RecoveryPhraseValidationFlowSnapshotTests/RecoveryPhraseValidationFlowSnapshotTests.swift b/secantTests/SnapshotTests/RecoveryPhraseValidationFlowSnapshotTests/RecoveryPhraseValidationFlowSnapshotTests.swift index 0ccdf2d..f9b8aba 100644 --- a/secantTests/SnapshotTests/RecoveryPhraseValidationFlowSnapshotTests/RecoveryPhraseValidationFlowSnapshotTests.swift +++ b/secantTests/SnapshotTests/RecoveryPhraseValidationFlowSnapshotTests/RecoveryPhraseValidationFlowSnapshotTests.swift @@ -14,8 +14,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase { func testRecoveryPhraseValidationFlowPreambleSnapshot() throws { let store = RecoveryPhraseValidationFlowStore( initialState: .placeholder, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) addAttachments(RecoveryPhraseValidationFlowView(store: store)) @@ -24,8 +23,8 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase { func testRecoveryPhraseValidationFlowBackupSnapshot() throws { let store = RecoveryPhraseValidationFlowStore( initialState: .placeholder, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() + .dependency(\.mainQueue, RecoveryPhraseValidationTests.testScheduler.eraseToAnyScheduler()) ) let viewStore = ViewStore(store) @@ -67,8 +66,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase { func testRecoveryPhraseValidationFlowSucceededSnapshot() throws { let store = RecoveryPhraseValidationFlowStore( initialState: .placeholder, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) addAttachments(RecoveryPhraseBackupSucceededView(store: store)) @@ -77,8 +75,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase { func testRecoveryPhraseValidationFlowFailedSnapshot() throws { let store = RecoveryPhraseValidationFlowStore( initialState: .placeholder, - reducer: .default, - environment: .demo + reducer: RecoveryPhraseValidationFlow() ) addAttachments(RecoveryPhraseBackupFailedView(store: store))