- 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 */; };
|
||||
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 = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1128,6 +1130,7 @@
|
|||
9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */,
|
||||
9EF1082A29114B93003D8097 /* Pasteboard.swift */,
|
||||
9EF1082C29114BCD003D8097 /* NewRecoveryPhrase.swift */,
|
||||
9E6EF2CA291287BB00CA007B /* FeedbackGenerator.swift */,
|
||||
);
|
||||
path = Dependencies;
|
||||
sourceTree = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
||||
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..<RecoveryPhraseValidationFlowState.phraseChunks).map { _ in
|
||||
Int.random(in: 0 ..< RecoveryPhraseValidationFlowState.wordGroupSize)
|
||||
return (0..<RecoveryPhraseValidationFlow.State.phraseChunks).map { _ in
|
||||
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
|
||||
|
||||
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
|
||||
typealias AnyAppReducer = AnyReducer<RecoveryPhraseDisplay.State, RecoveryPhraseDisplay.Action, AppEnvironment>
|
||||
typealias AppStore = Store<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
|
||||
|
||||
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(
|
||||
|
|
|
@ -9,43 +9,114 @@ import Foundation
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
|
||||
typealias RecoveryPhraseValidationFlowReducer = Reducer<
|
||||
RecoveryPhraseValidationFlowState,
|
||||
RecoveryPhraseValidationFlowAction,
|
||||
RecoveryPhraseValidationFlowEnvironment
|
||||
>
|
||||
typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
|
||||
typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
|
||||
typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlow.State, RecoveryPhraseValidationFlow.Action>
|
||||
typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlow.State, RecoveryPhraseValidationFlow.Action>
|
||||
|
||||
// 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
|
||||
@Dependency(\.randomPhrase) var randomPhrase
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.pasteboard) var pasteboard
|
||||
@Dependency(\.feedbackGenerator) var feedbackGenerator
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
var isValid: Bool {
|
||||
guard let resultingPhrase = self.resultingPhrase else { return false }
|
||||
return resultingPhrase == phrase.words
|
||||
// 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,
|
||||
/// 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<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
|
||||
|
||||
extension RecoveryPhraseValidationFlowViewStore {
|
||||
func bindingForRoute(_ route: RecoveryPhraseValidationFlowState.Route) -> Binding<Bool> {
|
||||
func bindingForRoute(_ route: RecoveryPhraseValidationFlow.State.Route) -> Binding<Bool> {
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -36,7 +36,7 @@ struct WordChipDropDelegate: DropDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension RecoveryPhraseValidationFlowState {
|
||||
extension RecoveryPhraseValidationFlow.State {
|
||||
func groupCompleted(index: Int) -> Bool {
|
||||
validationWords.first(where: { $0.groupIndex == index }) != nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
|
||||
struct WrappedRecoveryPhraseRandomizer {
|
||||
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowState
|
||||
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlow.State
|
||||
}
|
||||
|
||||
extension WrappedRecoveryPhraseRandomizer {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue