- flow is hidden by default - when turned on, only new users (nuke wallet and start over) continues to the flow - unit tests fixed and updated - new unit tests - phrase display screen simplified [#556] Hide post-seed backup flow and rework screenshot tests (#591) - never show the phrase for users who had it disabled at the time of wallet creation
This commit is contained in:
parent
2a560dea8d
commit
b61fa213b6
|
@ -446,6 +446,7 @@
|
|||
9E7FE0EC282E7D9400C374E8 /* TransactionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63B2818305D00BA3F17 /* TransactionState.swift */; };
|
||||
9E7FE0F628327F6F00C374E8 /* ScanUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */; };
|
||||
9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */; };
|
||||
9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */; };
|
||||
9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E92AF0728530EBF007367AD /* View+UIImage.swift */; };
|
||||
9E94C62028AA7DEE008256E9 /* BalanceBreakdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */; };
|
||||
9E94C62328AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */; };
|
||||
|
@ -768,6 +769,7 @@
|
|||
9E7FE0E5282E7B1100C374E8 /* StoredWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredWallet.swift; sourceTree = "<group>"; };
|
||||
9E7FE0F528327F6F00C374E8 /* ScanUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanUIView.swift; sourceTree = "<group>"; };
|
||||
9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScanView.swift; sourceTree = "<group>"; };
|
||||
9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidationFlowFeatureFlagTests.swift; sourceTree = "<group>"; };
|
||||
9E92AF0728530EBF007367AD /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = "<group>"; };
|
||||
9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownTests.swift; sourceTree = "<group>"; };
|
||||
9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -1091,7 +1093,6 @@
|
|||
children = (
|
||||
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */,
|
||||
0D6D628A276A528D002FB4CC /* DropDelegate.swift */,
|
||||
34F682E429A75EB60022C079 /* WalletConfig.swift */,
|
||||
9EF8139B27F47AED0075AF48 /* InitializationState.swift */,
|
||||
9E7FE0D6282D286500C374E8 /* RecoveryPhrase.swift */,
|
||||
9E612C7C2991476F00D09B09 /* SensitiveData.swift */,
|
||||
|
@ -1099,6 +1100,7 @@
|
|||
9E66122B2877188700C75B70 /* SyncStatusSnapshot.swift */,
|
||||
9E5BF63B2818305D00BA3F17 /* TransactionState.swift */,
|
||||
9E7FE0DC282D298900C374E8 /* ValidationWord.swift */,
|
||||
34F682E429A75EB60022C079 /* WalletConfig.swift */,
|
||||
9EAB46772860A1D2002904A0 /* WalletEvent.swift */,
|
||||
);
|
||||
path = Models;
|
||||
|
@ -1145,6 +1147,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0DFE93E5272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift */,
|
||||
9E852D5B29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift */,
|
||||
);
|
||||
path = RecoveryPhraseValidationTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3052,6 +3055,7 @@
|
|||
9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */,
|
||||
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */,
|
||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
||||
9E852D5C29AF8EB200CF4AC1 /* RecoveryPhraseValidationFlowFeatureFlagTests.swift in Sources */,
|
||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9E9ECC9A28589E150099D5A2 /* RecoveryPhraseValidationFlowSnapshotTests.swift in Sources */,
|
||||
9E0F574B2980260D005304FA /* LoggerTests.swift in Sources */,
|
||||
|
|
|
@ -15,5 +15,5 @@ extension DependencyValues {
|
|||
}
|
||||
|
||||
struct RecoveryPhraseRandomizerClient {
|
||||
let random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowReducer.State
|
||||
var random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowReducer.State
|
||||
}
|
||||
|
|
|
@ -116,10 +116,10 @@ struct WalletStorage {
|
|||
}
|
||||
}
|
||||
|
||||
func markUserPassedPhraseBackupTest() throws {
|
||||
func markUserPassedPhraseBackupTest(_ flag: Bool = true) throws {
|
||||
do {
|
||||
var wallet = try exportWallet()
|
||||
wallet.hasUserPassedPhraseBackupTest = true
|
||||
wallet.hasUserPassedPhraseBackupTest = flag
|
||||
|
||||
guard let data = try encode(object: wallet) else {
|
||||
throw KeychainError.encoding
|
||||
|
|
|
@ -30,6 +30,7 @@ struct WalletStorageClient {
|
|||
/// - bip39: Mnemonic/Seed phrase from `MnemonicSwift`
|
||||
/// - birthday: BlockHeight from SDK
|
||||
/// - language: Mnemonic's language
|
||||
/// - hasUserPassedPhraseBackupTest: If user passed the puzzle phrase backup
|
||||
/// - Throws:
|
||||
/// - `WalletStorageError.unsupportedLanguage`: when mnemonic's language is anything other than English
|
||||
/// - `WalletStorageError.alreadyImported` when valid wallet is already in the storage
|
||||
|
@ -66,7 +67,7 @@ struct WalletStorageClient {
|
|||
/// - Throws:
|
||||
/// - `WalletStorage.KeychainError.encoding`: when encoding the wallet's data failed.
|
||||
/// - `WalletStorageError.storageError` when some unrecognized error occurred.
|
||||
let markUserPassedPhraseBackupTest: () throws -> Void
|
||||
let markUserPassedPhraseBackupTest: (Bool) throws -> Void
|
||||
|
||||
/// Use carefully: deletes the stored wallet.
|
||||
/// There's no fate but what we make for ourselves - Sarah Connor.
|
||||
|
|
|
@ -32,8 +32,8 @@ extension WalletStorageClient: DependencyKey {
|
|||
updateBirthday: { birthday in
|
||||
try walletStorage.updateBirthday(birthday)
|
||||
},
|
||||
markUserPassedPhraseBackupTest: {
|
||||
try walletStorage.markUserPassedPhraseBackupTest()
|
||||
markUserPassedPhraseBackupTest: { flag in
|
||||
try walletStorage.markUserPassedPhraseBackupTest(flag)
|
||||
},
|
||||
nukeWallet: {
|
||||
walletStorage.nukeWallet()
|
||||
|
|
|
@ -25,7 +25,7 @@ extension WalletStorageClient {
|
|||
exportWallet: { .placeholder },
|
||||
areKeysPresent: { false },
|
||||
updateBirthday: { _ in },
|
||||
markUserPassedPhraseBackupTest: { },
|
||||
markUserPassedPhraseBackupTest: { _ in },
|
||||
nukeWallet: { }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ struct ImportWalletReducer: ReducerProtocol {
|
|||
try walletStorage.importWallet(state.importedSeedPhrase.data, birthday.data, .english, false)
|
||||
|
||||
// update the backup phrase validation flag
|
||||
try walletStorage.markUserPassedPhraseBackupTest()
|
||||
try walletStorage.markUserPassedPhraseBackupTest(true)
|
||||
|
||||
// notify user
|
||||
// TODO: [#221] Proper Error/Success handling (https://github.com/zcash/secant-ios-wallet/issues/221)
|
||||
|
|
|
@ -33,7 +33,6 @@ struct RecoveryPhraseDisplayReducer: ReducerProtocol {
|
|||
return .none
|
||||
|
||||
case .finishedPressed:
|
||||
// TODO: [#47] remove this when feature is implemented in https://github.com/zcash/secant-ios-wallet/issues/47
|
||||
return .none
|
||||
|
||||
case let .phraseResponse(phrase):
|
||||
|
|
|
@ -13,57 +13,57 @@ struct RecoveryPhraseDisplayView: View {
|
|||
|
||||
var body: some View {
|
||||
WithViewStore(self.store) { viewStore in
|
||||
ScrollView {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
if let groups = viewStore.phrase?.toGroups() {
|
||||
VStack(spacing: 20) {
|
||||
Text("recoveryPhraseDisplay.title")
|
||||
.titleText()
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
VStack(alignment: .center, spacing: 4) {
|
||||
Text("recoveryPhraseDisplay.description")
|
||||
.bodyText()
|
||||
|
||||
Text("recoveryPhraseDisplay.backItUp")
|
||||
.bodyText()
|
||||
}
|
||||
}
|
||||
.padding(.top, 0)
|
||||
.padding(.bottom, 20)
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
if let groups = viewStore.phrase?.toGroups(groupSizeOverride: 2) {
|
||||
VStack(spacing: 20) {
|
||||
Text("recoveryPhraseDisplay.title")
|
||||
.titleText()
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
VStack(alignment: .leading, spacing: 35) {
|
||||
ForEach(groups, id: \.startIndex) { group in
|
||||
VStack {
|
||||
WordChipGrid(words: group.words, startingAt: group.startIndex)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .center, spacing: 4) {
|
||||
Text("recoveryPhraseDisplay.description")
|
||||
.bodyText()
|
||||
}
|
||||
.padding(.horizontal, 5)
|
||||
|
||||
VStack {
|
||||
Button(
|
||||
action: { viewStore.send(.finishedPressed) },
|
||||
label: { Text("recoveryPhraseDisplay.button.finished") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 60)
|
||||
|
||||
Button(
|
||||
action: {
|
||||
viewStore.send(.copyToBufferPressed)
|
||||
},
|
||||
label: {
|
||||
Text("recoveryPhraseDisplay.button.copyToBuffer")
|
||||
.bodyText()
|
||||
}
|
||||
)
|
||||
.frame(height: 60)
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
Text("recoveryPhraseDisplay.noWords")
|
||||
}
|
||||
.padding(.top, 0)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
ForEach(groups, id: \.startIndex) { group in
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(group.startIndex). \(group.words[0].data)")
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading, 20)
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(group.startIndex + 1). \(group.words[1].data)")
|
||||
Spacer()
|
||||
}
|
||||
.padding(.trailing, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
Button(
|
||||
action: { viewStore.send(.finishedPressed) },
|
||||
label: { Text("recoveryPhraseDisplay.button.wroteItDown") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 60)
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
Text("recoveryPhraseDisplay.noWords")
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
|
|
|
@ -62,12 +62,15 @@ extension RootReducer {
|
|||
|
||||
case .phraseDisplay(.finishedPressed):
|
||||
// user is still supposed to do the backup phrase validation test
|
||||
if state.destinationState.previousDestination == .welcome
|
||||
|| state.destinationState.previousDestination == .onboarding {
|
||||
if (state.destinationState.previousDestination == .welcome
|
||||
|| state.destinationState.previousDestination == .onboarding
|
||||
|| state.destinationState.previousDestination == .startup)
|
||||
&& state.walletConfig.isEnabled(.testBackupPhraseFlow) {
|
||||
state.destinationState.destination = .phraseValidation
|
||||
}
|
||||
// user wanted to see the backup phrase once again (at validation finished screen)
|
||||
if state.destinationState.previousDestination == .phraseValidation {
|
||||
if state.destinationState.previousDestination == .phraseValidation
|
||||
|| !state.walletConfig.isEnabled(.testBackupPhraseFlow) {
|
||||
state.destinationState.destination = .home
|
||||
}
|
||||
|
||||
|
|
|
@ -149,8 +149,8 @@ extension RootReducer {
|
|||
}
|
||||
|
||||
var landingDestination = RootReducer.DestinationState.Destination.home
|
||||
|
||||
if !storedWallet.hasUserPassedPhraseBackupTest {
|
||||
|
||||
if !storedWallet.hasUserPassedPhraseBackupTest && state.walletConfig.isEnabled(.testBackupPhraseFlow) {
|
||||
do {
|
||||
let phraseWords = try mnemonic.asWords(storedWallet.seedPhrase.value())
|
||||
|
||||
|
@ -178,7 +178,7 @@ extension RootReducer {
|
|||
let birthday = zcashSDKEnvironment.latestCheckpoint
|
||||
|
||||
// store the wallet to the keychain
|
||||
try walletStorage.importWallet(newRandomPhrase, birthday, .english, false)
|
||||
try walletStorage.importWallet(newRandomPhrase, birthday, .english, !state.walletConfig.isEnabled(.testBackupPhraseFlow))
|
||||
|
||||
// start the backup phrase validation test
|
||||
let randomRecoveryPhraseWords = try mnemonic.asWords(newRandomPhrase)
|
||||
|
@ -198,7 +198,7 @@ extension RootReducer {
|
|||
|
||||
case .phraseValidation(.succeed):
|
||||
do {
|
||||
try walletStorage.markUserPassedPhraseBackupTest()
|
||||
try walletStorage.markUserPassedPhraseBackupTest(true)
|
||||
} catch {
|
||||
// TODO: [#221] error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
|
||||
}
|
||||
|
|
|
@ -23,10 +23,11 @@ struct RecoveryPhrase: Equatable, Redactable {
|
|||
|
||||
private let groupSize = 6
|
||||
|
||||
func toGroups() -> [Group] {
|
||||
let chunks = words.count / groupSize
|
||||
return zip(0 ..< chunks, words.chunked(into: groupSize)).map {
|
||||
Group(startIndex: $0 * groupSize + 1, words: $1)
|
||||
func toGroups(groupSizeOverride: Int? = nil) -> [Group] {
|
||||
let internalGroupSize = groupSizeOverride ?? groupSize
|
||||
let chunks = words.count / internalGroupSize
|
||||
return zip(0 ..< chunks, words.chunked(into: internalGroupSize)).map {
|
||||
Group(startIndex: $0 * internalGroupSize + 1, words: $1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,13 @@ enum FeatureFlag: String, CaseIterable, Codable {
|
|||
case testFlag1
|
||||
case testFlag2
|
||||
case onboardingFlow
|
||||
case testBackupPhraseFlow
|
||||
|
||||
var enabledByDefault: Bool {
|
||||
switch self {
|
||||
case .testFlag1, .testFlag2: return false
|
||||
case .onboardingFlow: return false
|
||||
case .testBackupPhraseFlow: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
|
||||
// MARK: - Secret Recovery Phrase Display
|
||||
"recoveryPhraseDisplay.title" = "Your Secret Recovery Phrase";
|
||||
"recoveryPhraseDisplay.description" = "The following 24 words represent your funds and the security used to protect them.";
|
||||
"recoveryPhraseDisplay.backItUp" = "Back them up now! There will be a test.";
|
||||
"recoveryPhraseDisplay.button.finished" = "Finished!";
|
||||
"recoveryPhraseDisplay.description" = "The following 24 words represent your funds and the security used to protect them. Back them up now!";
|
||||
"recoveryPhraseDisplay.button.wroteItDown" = "I wrote it down!";
|
||||
"recoveryPhraseDisplay.button.copyToBuffer" = "Copy To Buffer";
|
||||
"recoveryPhraseDisplay.noWords" = "Oops no words";
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ extension Text {
|
|||
func body(content: Content) -> some View {
|
||||
content
|
||||
.foregroundColor(Asset.Colors.Text.heading.color)
|
||||
.font(.custom(FontFamily.Rubik.medium.name, size: 33, relativeTo: .callout))
|
||||
.font(.custom(FontFamily.Rubik.medium.name, size: 24, relativeTo: .callout))
|
||||
.shadow(color: Asset.Colors.Text.captionTextShadow.color, radius: 1, x: 0, y: 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
//
|
||||
// RecoveryPhraseValidationFlowFeatureFlagTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 01.03.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import ComposableArchitecture
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
class RecoveryPhraseValidationFlowFeatureFlagTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
UserDefaultsWalletConfigStorage().clearAll()
|
||||
}
|
||||
|
||||
func testRecoveryPhraseValidationFlowOffByDefault() throws {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testBackupPhraseFlow))
|
||||
}
|
||||
|
||||
func testRecoveryPhraseValidationFlow_SkipPuzzleUserConfirmedBackup() {
|
||||
let store = TestStore(
|
||||
initialState: .placeholder,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
store.send(.phraseDisplay(.finishedPressed)) { state in
|
||||
state.destinationState.internalDestination = .home
|
||||
state.destinationState.previousDestination = .welcome
|
||||
}
|
||||
}
|
||||
|
||||
func testRecoveryPhraseValidationFlow_StartPuzzle() {
|
||||
var rootState = RootReducer.State.placeholder
|
||||
|
||||
var defaultRawFlags = WalletConfig.default.flags
|
||||
defaultRawFlags[.testBackupPhraseFlow] = true
|
||||
rootState.walletConfig = WalletConfig(flags: defaultRawFlags)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: rootState,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
store.send(.phraseDisplay(.finishedPressed))
|
||||
}
|
||||
|
||||
func testRecoveryPhraseValidationFlow_SkipPuzzleStartOfTheApp() {
|
||||
var rootState = RootReducer.State.placeholder
|
||||
rootState.storedWallet = .placeholder
|
||||
|
||||
let store = TestStore(
|
||||
initialState: rootState,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
|
||||
store.send(.initialization(.checkBackupPhraseValidation)) { state in
|
||||
state.appInitializationState = .initialized
|
||||
}
|
||||
|
||||
store.receive(.destination(.updateDestination(.home))) { state in
|
||||
state.destinationState.internalDestination = .home
|
||||
state.destinationState.previousDestination = .welcome
|
||||
}
|
||||
}
|
||||
|
||||
func testRecoveryPhraseValidationFlow_StartPuzzleStartOfTheApp() {
|
||||
var rootState = RootReducer.State.placeholder
|
||||
rootState.storedWallet = .placeholder
|
||||
|
||||
var defaultRawFlags = WalletConfig.default.flags
|
||||
defaultRawFlags[.testBackupPhraseFlow] = true
|
||||
rootState.walletConfig = WalletConfig(flags: defaultRawFlags)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: rootState,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
let mnemonic =
|
||||
"""
|
||||
still champion voice habit trend flight \
|
||||
survey between bitter process artefact blind \
|
||||
carbon truly provide dizzy crush flush \
|
||||
breeze blouse charge solid fish spread
|
||||
"""
|
||||
|
||||
let randomRecoveryPhrase = RecoveryPhraseValidationFlowReducer.State.placeholder
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.mnemonic = .noOp
|
||||
store.dependencies.mnemonic.asWords = { _ in mnemonic.components(separatedBy: " ") }
|
||||
store.dependencies.randomRecoveryPhrase.random = { _ in randomRecoveryPhrase }
|
||||
|
||||
store.send(.initialization(.checkBackupPhraseValidation)) { state in
|
||||
state.appInitializationState = .initialized
|
||||
state.phraseDisplayState.phrase = RecoveryPhrase(words: mnemonic.components(separatedBy: " ").map { $0.redacted })
|
||||
}
|
||||
|
||||
store.receive(.destination(.updateDestination(.phraseDisplay))) { state in
|
||||
state.destinationState.internalDestination = .phraseDisplay
|
||||
state.destinationState.previousDestination = .welcome
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor func testDidFinishLaunching_to_InitializedWalletPhraseBackupPuzzleOff() async throws {
|
||||
// setup the store and environment to be fully mocked
|
||||
let recoveryPhrase = RecoveryPhrase(words: try MnemonicClient.mock.randomMnemonicWords().map { $0.redacted })
|
||||
|
||||
let phraseValidationState = RecoveryPhraseValidationFlowReducer.State(
|
||||
phrase: recoveryPhrase,
|
||||
missingIndices: [2, 0, 3, 5],
|
||||
missingWordChips: [
|
||||
.unassigned(word: "voice".redacted),
|
||||
.empty,
|
||||
.unassigned(word: "survey".redacted),
|
||||
.unassigned(word: "spread".redacted)
|
||||
],
|
||||
validationWords: [
|
||||
.init(groupIndex: 2, word: "dizzy".redacted)
|
||||
],
|
||||
destination: nil
|
||||
)
|
||||
|
||||
let recoveryPhraseRandomizer = RecoveryPhraseRandomizerClient(
|
||||
random: { _ in
|
||||
let missingIndices = [2, 0, 3, 5]
|
||||
let missingWordChipKind = [
|
||||
PhraseChip.Kind.unassigned(
|
||||
word: "voice".redacted,
|
||||
color: Asset.Colors.Buttons.activeButton.color
|
||||
),
|
||||
PhraseChip.Kind.empty,
|
||||
PhraseChip.Kind.unassigned(
|
||||
word: "survey".redacted,
|
||||
color: Asset.Colors.Buttons.activeButton.color
|
||||
),
|
||||
PhraseChip.Kind.unassigned(
|
||||
word: "spread".redacted,
|
||||
color: Asset.Colors.Buttons.activeButton.color
|
||||
)
|
||||
]
|
||||
|
||||
return RecoveryPhraseValidationFlowReducer.State(
|
||||
phrase: recoveryPhrase,
|
||||
missingIndices: missingIndices,
|
||||
missingWordChips: missingWordChipKind,
|
||||
validationWords: [
|
||||
ValidationWord(
|
||||
groupIndex: 2,
|
||||
word: "dizzy".redacted
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
let appState = RootReducer.State(
|
||||
destinationState: .placeholder,
|
||||
homeState: .placeholder,
|
||||
onboardingState: .init(
|
||||
walletConfig: .default,
|
||||
importWalletState: .placeholder
|
||||
),
|
||||
phraseValidationState: phraseValidationState,
|
||||
phraseDisplayState: RecoveryPhraseDisplayReducer.State(
|
||||
phrase: recoveryPhrase
|
||||
),
|
||||
sandboxState: .placeholder,
|
||||
walletConfig: .default,
|
||||
welcomeState: .placeholder
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: appState,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
let testQueue = DispatchQueue.test
|
||||
|
||||
store.dependencies.databaseFiles = .noOp
|
||||
store.dependencies.databaseFiles.areDbFilesPresentFor = { _ in true }
|
||||
store.dependencies.derivationTool = .liveValue
|
||||
store.dependencies.mainQueue = .immediate// testQueue.eraseToAnyScheduler()
|
||||
store.dependencies.mnemonic = .mock
|
||||
store.dependencies.randomRecoveryPhrase = recoveryPhraseRandomizer
|
||||
store.dependencies.walletStorage.exportWallet = { .placeholder }
|
||||
store.dependencies.walletStorage.areKeysPresent = { true }
|
||||
store.dependencies.walletConfigProvider = .noOp
|
||||
|
||||
// Root of the test, the app finished the launch process and triggers the checks and initializations.
|
||||
await store.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
||||
await testQueue.advance(by: 0.02)
|
||||
|
||||
await store.receive(.initialization(.checkWalletConfig))
|
||||
|
||||
await store.receive(.walletConfigLoaded(WalletConfig.default))
|
||||
|
||||
await store.receive(.initialization(.initialSetups))
|
||||
|
||||
await testQueue.advance(by: 0.02)
|
||||
|
||||
await store.receive(.initialization(.configureCrashReporter))
|
||||
|
||||
await store.receive(.initialization(.checkWalletInitialization))
|
||||
|
||||
await store.receive(.initialization(.respondToWalletInitializationState(.initialized)))
|
||||
|
||||
await testQueue.advance(by: 3.00)
|
||||
|
||||
await store.receive(.initialization(.initializeSDK)) { state in
|
||||
state.storedWallet = .placeholder
|
||||
}
|
||||
|
||||
await store.receive(.initialization(.checkBackupPhraseValidation)) { state in
|
||||
state.appInitializationState = .initialized
|
||||
}
|
||||
|
||||
await store.receive(.destination(.updateDestination(.home))) { state in
|
||||
state.destinationState.previousDestination = .welcome
|
||||
state.destinationState.internalDestination = .home
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
}
|
|
@ -63,6 +63,10 @@ class AppInitializationTests: XCTestCase {
|
|||
}
|
||||
)
|
||||
|
||||
var defaultRawFlags = WalletConfig.default.flags
|
||||
defaultRawFlags[.testBackupPhraseFlow] = true
|
||||
let walletConfig = WalletConfig(flags: defaultRawFlags)
|
||||
|
||||
let appState = RootReducer.State(
|
||||
destinationState: .placeholder,
|
||||
homeState: .placeholder,
|
||||
|
@ -75,7 +79,7 @@ class AppInitializationTests: XCTestCase {
|
|||
phrase: recoveryPhrase
|
||||
),
|
||||
sandboxState: .placeholder,
|
||||
walletConfig: .default,
|
||||
walletConfig: walletConfig,
|
||||
welcomeState: .placeholder
|
||||
)
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class SettingsTests: XCTestCase {
|
|||
updateBirthday: { _ in
|
||||
throw WalletStorage.KeychainError.encoding
|
||||
},
|
||||
markUserPassedPhraseBackupTest: {
|
||||
markUserPassedPhraseBackupTest: { _ in
|
||||
throw WalletStorage.KeychainError.encoding
|
||||
},
|
||||
nukeWallet: { }
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Combine
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import ComposableArchitecture
|
||||
|
||||
class WalletConfigProviderTests: XCTestCase {
|
||||
var cancellables: [AnyCancellable] = []
|
||||
|
@ -114,6 +115,24 @@ class WalletConfigProviderTests: XCTestCase {
|
|||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func testPropagationOfFlagUpdate() throws {
|
||||
let store = TestStore(
|
||||
initialState: .placeholder,
|
||||
reducer: RootReducer()
|
||||
)
|
||||
|
||||
// Change any of the flags from the default value
|
||||
var defaultRawFlags = WalletConfig.default.flags
|
||||
defaultRawFlags[.onboardingFlow] = true
|
||||
let flags = WalletConfig(flags: defaultRawFlags)
|
||||
|
||||
// The new flag's value has to be propagated to all `walletConfig` instances
|
||||
store.send(.debug(.walletConfigLoaded(flags))) { state in
|
||||
state.walletConfig = flags
|
||||
state.onboardingState.walletConfig = flags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WalletConfigSourceProviderMock: WalletConfigSourceProvider {
|
||||
|
|
Loading…
Reference in New Issue