- RecoveryPhraseValidationFlow migrated to the ReducerProtocol - unit tests fixed
This commit is contained in:
parent
f7be225e01
commit
d44eb5ef1b
|
@ -144,6 +144,7 @@
|
||||||
9E6713F8289BC58C00A6796F /* BalanceBreakdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */; };
|
9E6713F8289BC58C00A6796F /* BalanceBreakdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */; };
|
||||||
9E6713FA289BE0E100A6796F /* ClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */; };
|
9E6713FA289BE0E100A6796F /* ClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */; };
|
||||||
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.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 */; };
|
9E7225F12889539300DF7F17 /* SettingsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */; };
|
||||||
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */; };
|
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */; };
|
||||||
9E7225F6288AC71A00DF7F17 /* MultiLineTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.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 = "<group>"; };
|
9E6713F6289BC58C00A6796F /* BalanceBreakdownStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownStore.swift; sourceTree = "<group>"; };
|
||||||
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearBackgroundView.swift; sourceTree = "<group>"; };
|
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearBackgroundView.swift; sourceTree = "<group>"; };
|
||||||
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
|
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
|
||||||
|
9E6EF2CA291287BB00CA007B /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = "<group>"; };
|
||||||
9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSnapshotTests.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>"; };
|
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>"; };
|
9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldStore.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1128,6 +1130,7 @@
|
||||||
9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */,
|
9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */,
|
||||||
9EF1082A29114B93003D8097 /* Pasteboard.swift */,
|
9EF1082A29114B93003D8097 /* Pasteboard.swift */,
|
||||||
9EF1082C29114BCD003D8097 /* NewRecoveryPhrase.swift */,
|
9EF1082C29114BCD003D8097 /* NewRecoveryPhrase.swift */,
|
||||||
|
9E6EF2CA291287BB00CA007B /* FeedbackGenerator.swift */,
|
||||||
);
|
);
|
||||||
path = Dependencies;
|
path = Dependencies;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1671,6 +1674,7 @@
|
||||||
9E7CB6292875AC2D00A02233 /* AppVersionHandler.swift in Sources */,
|
9E7CB6292875AC2D00A02233 /* AppVersionHandler.swift in Sources */,
|
||||||
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
|
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
|
||||||
9EAB467A2861EA6A002904A0 /* TransactionRowView.swift in Sources */,
|
9EAB467A2861EA6A002904A0 /* TransactionRowView.swift in Sources */,
|
||||||
|
9E6EF2CB291287BB00CA007B /* FeedbackGenerator.swift in Sources */,
|
||||||
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */,
|
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */,
|
||||||
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */,
|
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */,
|
||||||
0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */,
|
0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */,
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,11 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct RecoveryPhraseRandomizer {
|
struct RecoveryPhraseRandomizer {
|
||||||
func random(phrase: RecoveryPhrase) -> RecoveryPhraseValidationFlowState {
|
func random(phrase: RecoveryPhrase) -> RecoveryPhraseValidationFlow.State {
|
||||||
let missingIndices = randomIndices()
|
let missingIndices = randomIndices()
|
||||||
let missingWordChipKind = phrase.words(fromMissingIndices: missingIndices).shuffled()
|
let missingWordChipKind = phrase.words(fromMissingIndices: missingIndices).shuffled()
|
||||||
|
|
||||||
return RecoveryPhraseValidationFlowState(
|
return RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: missingWordChipKind,
|
missingWordChips: missingWordChipKind,
|
||||||
|
@ -21,8 +21,21 @@ struct RecoveryPhraseRandomizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomIndices() -> [Int] {
|
func randomIndices() -> [Int] {
|
||||||
return (0..<RecoveryPhraseValidationFlowState.phraseChunks).map { _ in
|
return (0..<RecoveryPhraseValidationFlow.State.phraseChunks).map { _ in
|
||||||
Int.random(in: 0 ..< RecoveryPhraseValidationFlowState.wordGroupSize)
|
Int.random(in: 0 ..< RecoveryPhraseValidationFlow.State.wordGroupSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
private enum RecoveryPhraseRandomKey: DependencyKey {
|
||||||
|
static let liveValue = WrappedRecoveryPhraseRandomizer.live
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
var randomPhrase: WrappedRecoveryPhraseRandomizer {
|
||||||
|
get { self[RecoveryPhraseRandomKey.self] }
|
||||||
|
set { self[RecoveryPhraseRandomKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ import ZcashLightClientKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
|
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
|
||||||
typealias AnyAppReducer = AnyReducer<RecoveryPhraseDisplay.State, RecoveryPhraseDisplay.Action, AppEnvironment>
|
|
||||||
typealias AppStore = Store<AppState, AppAction>
|
typealias AppStore = Store<AppState, AppAction>
|
||||||
typealias AppViewStore = ViewStore<AppState, AppAction>
|
typealias AppViewStore = ViewStore<AppState, AppAction>
|
||||||
|
|
||||||
|
typealias AnyRecoveryPhraseDisplayReducer = AnyReducer<RecoveryPhraseDisplay.State, RecoveryPhraseDisplay.Action, AppEnvironment>
|
||||||
|
typealias AnyRecoveryPhraseValidationFlowReducer = AnyReducer<RecoveryPhraseValidationFlow.State, RecoveryPhraseValidationFlow.Action, AppEnvironment>
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
struct AppState: Equatable {
|
struct AppState: Equatable {
|
||||||
|
@ -23,7 +25,7 @@ struct AppState: Equatable {
|
||||||
var appInitializationState: InitializationState = .uninitialized
|
var appInitializationState: InitializationState = .uninitialized
|
||||||
var homeState: HomeState
|
var homeState: HomeState
|
||||||
var onboardingState: OnboardingFlowState
|
var onboardingState: OnboardingFlowState
|
||||||
var phraseValidationState: RecoveryPhraseValidationFlowState
|
var phraseValidationState: RecoveryPhraseValidationFlow.State
|
||||||
var phraseDisplayState: RecoveryPhraseDisplay.State
|
var phraseDisplayState: RecoveryPhraseDisplay.State
|
||||||
var prevRoute: Route?
|
var prevRoute: Route?
|
||||||
var internalRoute: Route = .welcome
|
var internalRoute: Route = .welcome
|
||||||
|
@ -55,7 +57,7 @@ enum AppAction: Equatable {
|
||||||
case nukeWallet
|
case nukeWallet
|
||||||
case onboarding(OnboardingFlowAction)
|
case onboarding(OnboardingFlowAction)
|
||||||
case phraseDisplay(RecoveryPhraseDisplay.Action)
|
case phraseDisplay(RecoveryPhraseDisplay.Action)
|
||||||
case phraseValidation(RecoveryPhraseValidationFlowAction)
|
case phraseValidation(RecoveryPhraseValidationFlow.Action)
|
||||||
case respondToWalletInitializationState(InitializationState)
|
case respondToWalletInitializationState(InitializationState)
|
||||||
case sandbox(SandboxAction)
|
case sandbox(SandboxAction)
|
||||||
case updateRoute(AppState.Route)
|
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,
|
state: \AppState.phraseValidationState,
|
||||||
action: /AppAction.phraseValidation,
|
action: /AppAction.phraseValidation,
|
||||||
environment: { environment in
|
environment: { $0 }
|
||||||
RecoveryPhraseValidationFlowEnvironment(
|
|
||||||
scheduler: environment.scheduler,
|
|
||||||
pasteboard: .test,
|
|
||||||
feedbackGenerator: .silent,
|
|
||||||
recoveryPhraseRandomizer: environment.recoveryPhraseRandomizer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private static let phraseDisplayReducer: AppReducer = AnyAppReducer { _ in
|
private static let phraseDisplayReducer: AppReducer = AnyRecoveryPhraseDisplayReducer { _ in
|
||||||
RecoveryPhraseDisplay()
|
RecoveryPhraseDisplay()
|
||||||
}
|
}
|
||||||
.pullback(
|
.pullback(
|
||||||
|
|
|
@ -9,43 +9,114 @@ import Foundation
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias RecoveryPhraseValidationFlowReducer = Reducer<
|
typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlow.State, RecoveryPhraseValidationFlow.Action>
|
||||||
RecoveryPhraseValidationFlowState,
|
typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlow.State, RecoveryPhraseValidationFlow.Action>
|
||||||
RecoveryPhraseValidationFlowAction,
|
|
||||||
RecoveryPhraseValidationFlowEnvironment
|
|
||||||
>
|
|
||||||
typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
|
|
||||||
typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
|
|
||||||
|
|
||||||
// MARK: - State
|
struct RecoveryPhraseValidationFlow: ReducerProtocol {
|
||||||
|
struct State: Equatable {
|
||||||
|
enum Route: Equatable, CaseIterable {
|
||||||
|
case validation
|
||||||
|
case success
|
||||||
|
case failure
|
||||||
|
}
|
||||||
|
|
||||||
struct RecoveryPhraseValidationFlowState: Equatable {
|
static let wordGroupSize = 6
|
||||||
enum Route: Equatable, CaseIterable {
|
static let phraseChunks = 4
|
||||||
case validation
|
|
||||||
case success
|
var phrase: RecoveryPhrase
|
||||||
case failure
|
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 {
|
@Dependency(\.randomPhrase) var randomPhrase
|
||||||
!validationWords.isEmpty && validationWords.count == missingIndices.count
|
@Dependency(\.mainQueue) var mainQueue
|
||||||
}
|
@Dependency(\.pasteboard) var pasteboard
|
||||||
|
@Dependency(\.feedbackGenerator) var feedbackGenerator
|
||||||
|
|
||||||
var isValid: Bool {
|
enum Action: Equatable {
|
||||||
guard let resultingPhrase = self.resultingPhrase else { return false }
|
case updateRoute(RecoveryPhraseValidationFlow.State.Route?)
|
||||||
return resultingPhrase == phrase.words
|
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<Action> {
|
||||||
|
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<RecoveryPhraseValidationFlow.Action, Never>(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,
|
/// 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.
|
/// 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`
|
/// - 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<DispatchQueue>
|
|
||||||
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<RecoveryPhraseValidationFlowAction, Never>(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
|
// MARK: - ViewStore
|
||||||
|
|
||||||
extension RecoveryPhraseValidationFlowViewStore {
|
extension RecoveryPhraseValidationFlowViewStore {
|
||||||
func bindingForRoute(_ route: RecoveryPhraseValidationFlowState.Route) -> Binding<Bool> {
|
func bindingForRoute(_ route: RecoveryPhraseValidationFlow.State.Route) -> Binding<Bool> {
|
||||||
self.binding(
|
self.binding(
|
||||||
get: { $0.route == route },
|
get: { $0.route == route },
|
||||||
send: { isActive in
|
send: { isActive in
|
||||||
|
@ -240,8 +210,8 @@ extension RecoveryPhraseValidationFlowViewStore {
|
||||||
|
|
||||||
// MARK: - Placeholders
|
// MARK: - Placeholders
|
||||||
|
|
||||||
extension RecoveryPhraseValidationFlowState {
|
extension RecoveryPhraseValidationFlow.State {
|
||||||
static let placeholder = RecoveryPhraseValidationFlowState(
|
static let placeholder = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: .placeholder,
|
phrase: .placeholder,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -254,7 +224,7 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
route: nil
|
route: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
static let placeholderStep1 = RecoveryPhraseValidationFlowState(
|
static let placeholderStep1 = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: .placeholder,
|
phrase: .placeholder,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -269,7 +239,7 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
route: nil
|
route: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
static let placeholderStep2 = RecoveryPhraseValidationFlowState(
|
static let placeholderStep2 = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: .placeholder,
|
phrase: .placeholder,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -285,7 +255,7 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
route: nil
|
route: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
static let placeholderStep3 = RecoveryPhraseValidationFlowState(
|
static let placeholderStep3 = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: .placeholder,
|
phrase: .placeholder,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -302,7 +272,7 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
route: nil
|
route: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
static let placeholderStep4 = RecoveryPhraseValidationFlowState(
|
static let placeholderStep4 = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: .placeholder,
|
phrase: .placeholder,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -324,31 +294,26 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
extension RecoveryPhraseValidationFlowStore {
|
extension RecoveryPhraseValidationFlowStore {
|
||||||
static let demo = Store(
|
static let demo = Store(
|
||||||
initialState: .placeholder,
|
initialState: .placeholder,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
static let demoStep1 = Store(
|
static let demoStep1 = Store(
|
||||||
initialState: .placeholderStep1,
|
initialState: .placeholderStep1,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
static let demoStep2 = Store(
|
static let demoStep2 = Store(
|
||||||
initialState: .placeholderStep1,
|
initialState: .placeholderStep1,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
static let demoStep3 = Store(
|
static let demoStep3 = Store(
|
||||||
initialState: .placeholderStep3,
|
initialState: .placeholderStep3,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
static let demoStep4 = Store(
|
static let demoStep4 = Store(
|
||||||
initialState: .placeholderStep4,
|
initialState: .placeholderStep4,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ private extension RecoveryPhraseBackupView {
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlowState) -> some View {
|
@ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlow.State) -> some View {
|
||||||
if state.isValid {
|
if state.isValid {
|
||||||
Text("recoveryPhraseBackupValidation.successResult")
|
Text("recoveryPhraseBackupValidation.successResult")
|
||||||
.bodyText()
|
.bodyText()
|
||||||
|
@ -94,7 +94,7 @@ private extension RecoveryPhraseBackupView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension RecoveryPhraseValidationFlowState {
|
private extension RecoveryPhraseValidationFlow.State {
|
||||||
@ViewBuilder func missingWordGrid() -> some View {
|
@ViewBuilder func missingWordGrid() -> some View {
|
||||||
let columns = Array(
|
let columns = Array(
|
||||||
repeating: GridItem(.flexible(minimum: 100, maximum: 120), spacing: 20),
|
repeating: GridItem(.flexible(minimum: 100, maximum: 120), spacing: 20),
|
||||||
|
@ -116,7 +116,7 @@ private extension RecoveryPhraseValidationFlowState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RecoveryPhraseValidationFlowState {
|
extension RecoveryPhraseValidationFlow.State {
|
||||||
func wordsChips(
|
func wordsChips(
|
||||||
for groupIndex: Int,
|
for groupIndex: Int,
|
||||||
groupSize: Int,
|
groupSize: Int,
|
||||||
|
@ -140,14 +140,14 @@ extension RecoveryPhraseValidationFlowState {
|
||||||
|
|
||||||
private extension WordChipGrid {
|
private extension WordChipGrid {
|
||||||
init(
|
init(
|
||||||
state: RecoveryPhraseValidationFlowState,
|
state: RecoveryPhraseValidationFlow.State,
|
||||||
groupIndex: Int,
|
groupIndex: Int,
|
||||||
wordGroup: RecoveryPhrase.Group,
|
wordGroup: RecoveryPhrase.Group,
|
||||||
misingIndex: Int
|
misingIndex: Int
|
||||||
) {
|
) {
|
||||||
let chips = state.wordsChips(
|
let chips = state.wordsChips(
|
||||||
for: groupIndex,
|
for: groupIndex,
|
||||||
groupSize: RecoveryPhraseValidationFlowState.wordGroupSize,
|
groupSize: RecoveryPhraseValidationFlow.State.wordGroupSize,
|
||||||
from: wordGroup
|
from: wordGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ private extension WordChipGrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension RecoveryPhraseValidationFlowState {
|
private extension RecoveryPhraseValidationFlow.State {
|
||||||
var coloredChipColor: Color {
|
var coloredChipColor: Color {
|
||||||
if self.isComplete {
|
if self.isComplete {
|
||||||
return isValid ? Asset.Colors.Buttons.activeButton.color : Asset.Colors.BackgroundColors.red.color
|
return isValid ? Asset.Colors.Buttons.activeButton.color : Asset.Colors.BackgroundColors.red.color
|
||||||
|
|
|
@ -36,7 +36,7 @@ struct WordChipDropDelegate: DropDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RecoveryPhraseValidationFlowState {
|
extension RecoveryPhraseValidationFlow.State {
|
||||||
func groupCompleted(index: Int) -> Bool {
|
func groupCompleted(index: Int) -> Bool {
|
||||||
validationWords.first(where: { $0.groupIndex == index }) != nil
|
validationWords.first(where: { $0.groupIndex == index }) != nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct WrappedRecoveryPhraseRandomizer {
|
struct WrappedRecoveryPhraseRandomizer {
|
||||||
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowState
|
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlow.State
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WrappedRecoveryPhraseRandomizer {
|
extension WrappedRecoveryPhraseRandomizer {
|
||||||
|
|
|
@ -33,7 +33,7 @@ class AppInitializationTests: XCTestCase {
|
||||||
|
|
||||||
let recoveryPhrase = RecoveryPhrase(words: try WrappedMnemonic.mock.randomMnemonicWords())
|
let recoveryPhrase = RecoveryPhrase(words: try WrappedMnemonic.mock.randomMnemonicWords())
|
||||||
|
|
||||||
let phraseValidationState = RecoveryPhraseValidationFlowState(
|
let phraseValidationState = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: recoveryPhrase,
|
phrase: recoveryPhrase,
|
||||||
missingIndices: [2, 0, 3, 5],
|
missingIndices: [2, 0, 3, 5],
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -67,7 +67,7 @@ class AppInitializationTests: XCTestCase {
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
return RecoveryPhraseValidationFlowState(
|
return RecoveryPhraseValidationFlow.State(
|
||||||
phrase: recoveryPhrase,
|
phrase: recoveryPhrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: missingWordChipKind,
|
missingWordChips: missingWordChipKind,
|
||||||
|
|
|
@ -12,13 +12,6 @@ import ComposableArchitecture
|
||||||
class RecoveryPhraseValidationTests: XCTestCase {
|
class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
static let testScheduler = DispatchQueue.test
|
static let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
let testEnvironment = RecoveryPhraseValidationFlowEnvironment(
|
|
||||||
scheduler: testScheduler.eraseToAnyScheduler(),
|
|
||||||
pasteboard: .test,
|
|
||||||
feedbackGenerator: .silent,
|
|
||||||
recoveryPhraseRandomizer: .live
|
|
||||||
)
|
|
||||||
|
|
||||||
func testPickWordsFromMissingIndices() throws {
|
func testPickWordsFromMissingIndices() throws {
|
||||||
let words = [
|
let words = [
|
||||||
"bring", "salute", "thank",
|
"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 missingWordChips: [PhraseChip.Kind] = ["salute", "boil", "cancel", "pizza"].map({ PhraseChip.Kind.unassigned(word: $0) })
|
||||||
|
|
||||||
let initialStep = RecoveryPhraseValidationFlowState(
|
let initialStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: missingWordChips,
|
missingWordChips: missingWordChips,
|
||||||
validationWords: []
|
validationWords: []
|
||||||
)
|
)
|
||||||
|
|
||||||
let store = TestStore(initialState: initialStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment)
|
let store = TestStore(
|
||||||
|
initialState: initialStep,
|
||||||
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
|
)
|
||||||
|
|
||||||
let expectedMissingChips = [
|
let expectedMissingChips = [
|
||||||
PhraseChip.Kind.empty,
|
PhraseChip.Kind.empty,
|
||||||
|
@ -113,13 +109,16 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let missingWordChips = ["salute", "boil", "cancel", "pizza"].map({ PhraseChip.Kind.unassigned(word: $0) })
|
let missingWordChips = ["salute", "boil", "cancel", "pizza"].map({ PhraseChip.Kind.unassigned(word: $0) })
|
||||||
|
|
||||||
let initialStep = RecoveryPhraseValidationFlowState.initial(
|
let initialStep = RecoveryPhraseValidationFlow.State.initial(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordsChips: missingWordChips
|
missingWordsChips: missingWordChips
|
||||||
)
|
)
|
||||||
|
|
||||||
let store = TestStore(initialState: initialStep, reducer: RecoveryPhraseValidationFlowReducer.default, environment: testEnvironment)
|
let store = TestStore(
|
||||||
|
initialState: initialStep,
|
||||||
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
|
)
|
||||||
|
|
||||||
let expectedMissingChips = [
|
let expectedMissingChips = [
|
||||||
PhraseChip.Kind.unassigned(word: "salute"),
|
PhraseChip.Kind.unassigned(word: "salute"),
|
||||||
|
@ -157,7 +156,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -169,7 +168,10 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
validationWords: [ValidationWord(groupIndex: 0, word: "salute")]
|
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 = [
|
let expectedMissingWordChips = [
|
||||||
PhraseChip.Kind.empty,
|
PhraseChip.Kind.empty,
|
||||||
|
@ -210,7 +212,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
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 = [
|
let expectedMissingWordChips = [
|
||||||
PhraseChip.Kind.empty,
|
PhraseChip.Kind.empty,
|
||||||
|
@ -267,7 +272,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
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 = [
|
let expectedMissingWordChips = [
|
||||||
PhraseChip.Kind.empty,
|
PhraseChip.Kind.empty,
|
||||||
|
@ -334,7 +343,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
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 = [
|
let expectedMissingWordChips = [
|
||||||
PhraseChip.Kind.empty,
|
PhraseChip.Kind.empty,
|
||||||
|
@ -402,7 +415,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -454,7 +467,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -507,7 +520,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -545,7 +558,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
|
|
||||||
let phrase = RecoveryPhrase(words: words)
|
let phrase = RecoveryPhrase(words: words)
|
||||||
|
|
||||||
let currentStep = RecoveryPhraseValidationFlowState(
|
let currentStep = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: [
|
missingWordChips: [
|
||||||
|
@ -591,7 +604,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
ValidationWord(groupIndex: 3, word: "pizza")
|
ValidationWord(groupIndex: 3, word: "pizza")
|
||||||
]
|
]
|
||||||
|
|
||||||
let result = RecoveryPhraseValidationFlowState(
|
let result = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
|
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
|
||||||
|
@ -630,7 +643,7 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
ValidationWord(groupIndex: 2, word: "pizza")
|
ValidationWord(groupIndex: 2, word: "pizza")
|
||||||
]
|
]
|
||||||
|
|
||||||
let result = RecoveryPhraseValidationFlowState(
|
let result = RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
|
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
|
||||||
|
@ -644,13 +657,13 @@ class RecoveryPhraseValidationTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RecoveryPhraseValidationFlowState {
|
extension RecoveryPhraseValidationFlow.State {
|
||||||
static func initial(
|
static func initial(
|
||||||
phrase: RecoveryPhrase,
|
phrase: RecoveryPhrase,
|
||||||
missingIndices: [Int],
|
missingIndices: [Int],
|
||||||
missingWordsChips: [PhraseChip.Kind]
|
missingWordsChips: [PhraseChip.Kind]
|
||||||
) -> Self {
|
) -> Self {
|
||||||
RecoveryPhraseValidationFlowState(
|
RecoveryPhraseValidationFlow.State(
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
missingIndices: missingIndices,
|
missingIndices: missingIndices,
|
||||||
missingWordChips: missingWordsChips,
|
missingWordChips: missingWordsChips,
|
||||||
|
|
|
@ -14,8 +14,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase {
|
||||||
func testRecoveryPhraseValidationFlowPreambleSnapshot() throws {
|
func testRecoveryPhraseValidationFlowPreambleSnapshot() throws {
|
||||||
let store = RecoveryPhraseValidationFlowStore(
|
let store = RecoveryPhraseValidationFlowStore(
|
||||||
initialState: .placeholder,
|
initialState: .placeholder,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
addAttachments(RecoveryPhraseValidationFlowView(store: store))
|
addAttachments(RecoveryPhraseValidationFlowView(store: store))
|
||||||
|
@ -24,8 +23,8 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase {
|
||||||
func testRecoveryPhraseValidationFlowBackupSnapshot() throws {
|
func testRecoveryPhraseValidationFlowBackupSnapshot() throws {
|
||||||
let store = RecoveryPhraseValidationFlowStore(
|
let store = RecoveryPhraseValidationFlowStore(
|
||||||
initialState: .placeholder,
|
initialState: .placeholder,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
.dependency(\.mainQueue, RecoveryPhraseValidationTests.testScheduler.eraseToAnyScheduler())
|
||||||
)
|
)
|
||||||
let viewStore = ViewStore(store)
|
let viewStore = ViewStore(store)
|
||||||
|
|
||||||
|
@ -67,8 +66,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase {
|
||||||
func testRecoveryPhraseValidationFlowSucceededSnapshot() throws {
|
func testRecoveryPhraseValidationFlowSucceededSnapshot() throws {
|
||||||
let store = RecoveryPhraseValidationFlowStore(
|
let store = RecoveryPhraseValidationFlowStore(
|
||||||
initialState: .placeholder,
|
initialState: .placeholder,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
addAttachments(RecoveryPhraseBackupSucceededView(store: store))
|
addAttachments(RecoveryPhraseBackupSucceededView(store: store))
|
||||||
|
@ -77,8 +75,7 @@ class RecoveryPhraseValidationFlowSnapshotTests: XCTestCase {
|
||||||
func testRecoveryPhraseValidationFlowFailedSnapshot() throws {
|
func testRecoveryPhraseValidationFlowFailedSnapshot() throws {
|
||||||
let store = RecoveryPhraseValidationFlowStore(
|
let store = RecoveryPhraseValidationFlowStore(
|
||||||
initialState: .placeholder,
|
initialState: .placeholder,
|
||||||
reducer: .default,
|
reducer: RecoveryPhraseValidationFlow()
|
||||||
environment: .demo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
addAttachments(RecoveryPhraseBackupFailedView(store: store))
|
addAttachments(RecoveryPhraseBackupFailedView(store: store))
|
||||||
|
|
Loading…
Reference in New Issue