[#445] Migrate RecoveryPhraseValidationFlowStore to ReducerProtocol (#446)

- RecoveryPhraseValidationFlow migrated to the ReducerProtocol
- unit tests fixed
This commit is contained in:
Lukas Korba 2022-11-02 17:43:42 +01:00 committed by GitHub
parent f7be225e01
commit d44eb5ef1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 221 additions and 211 deletions

View File

@ -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 */,

View File

@ -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 }
}
}

View File

@ -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 }
}
}

View File

@ -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(

View File

@ -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
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<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()
)
}

View File

@ -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

View File

@ -36,7 +36,7 @@ struct WordChipDropDelegate: DropDelegate {
}
}
extension RecoveryPhraseValidationFlowState {
extension RecoveryPhraseValidationFlow.State {
func groupCompleted(index: Int) -> Bool {
validationWords.first(where: { $0.groupIndex == index }) != nil
}

View File

@ -8,7 +8,7 @@
import Foundation
struct WrappedRecoveryPhraseRandomizer {
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowState
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlow.State
}
extension WrappedRecoveryPhraseRandomizer {

View File

@ -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,

View File

@ -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,

View File

@ -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))