parent
56e1364659
commit
148ca941f1
|
@ -17,4 +17,5 @@ extension DependencyValues {
|
|||
|
||||
struct WalletConfigProviderClient {
|
||||
let load: () async -> WalletConfig
|
||||
let update: (FeatureFlag, Bool) async -> Void
|
||||
}
|
||||
|
|
|
@ -19,6 +19,11 @@ extension WalletConfigProviderClient: DependencyKey {
|
|||
}
|
||||
|
||||
static func live(walletConfigProvider: WalletConfigProvider = WalletConfigProviderClient.defaultWalletConfigProvider) -> Self {
|
||||
Self(load: { return await walletConfigProvider.load() })
|
||||
Self(
|
||||
load: { return await walletConfigProvider.load() },
|
||||
update: { flag, isEnabled in
|
||||
await walletConfigProvider.update(featureFlag: flag, isEnabled: isEnabled)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,14 @@ import XCTestDynamicOverlay
|
|||
|
||||
extension WalletConfigProviderClient: TestDependencyKey {
|
||||
static let testValue = Self(
|
||||
load: XCTUnimplemented("\(Self.self).load", placeholder: WalletConfig.default)
|
||||
load: XCTUnimplemented("\(Self.self).load", placeholder: WalletConfig.default),
|
||||
update: XCTUnimplemented("\(Self.self).update")
|
||||
)
|
||||
}
|
||||
|
||||
extension WalletConfigProviderClient {
|
||||
static let noOp = Self(
|
||||
load: { WalletConfig.default }
|
||||
load: { WalletConfig.default },
|
||||
update: { _, _ in }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ extension RootReducer {
|
|||
}
|
||||
return EffectTask(value: .destination(.deeplink(url)))
|
||||
|
||||
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome:
|
||||
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome, .debug:
|
||||
return .none
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@ extension RootReducer {
|
|||
case walletConfigChanged(WalletConfig)
|
||||
}
|
||||
|
||||
enum DebugAction: Equatable {
|
||||
case updateFlag(FeatureFlag, Bool)
|
||||
case flagUpdated(WalletConfig)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||
func initializationReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
|
||||
Reduce { state, action in
|
||||
|
@ -45,8 +50,7 @@ extension RootReducer {
|
|||
}
|
||||
|
||||
case .initialization(.walletConfigChanged(let walletConfig)):
|
||||
state.walletConfig = walletConfig
|
||||
state.onboardingState.walletConfig = walletConfig
|
||||
updateStateAfterConfigUpdate(state: &state, config: walletConfig)
|
||||
return EffectTask(value: .initialization(.initialSetups))
|
||||
|
||||
case .initialization(.initialSetups):
|
||||
|
@ -227,7 +231,23 @@ extension RootReducer {
|
|||
!userStoredPreferences.isUserOptedOutOfCrashReporting()
|
||||
)
|
||||
return .none
|
||||
|
||||
case let .debug(.updateFlag(flag, isEnabled)):
|
||||
return .run { send in
|
||||
await walletConfigProvider.update(flag, !isEnabled)
|
||||
let walletConfig = await walletConfigProvider.load()
|
||||
await send(.debug(.flagUpdated(walletConfig)))
|
||||
}
|
||||
|
||||
case let .debug(.flagUpdated(walletConfig)):
|
||||
updateStateAfterConfigUpdate(state: &state, config: walletConfig)
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStateAfterConfigUpdate(state: inout RootReducer.State, config: WalletConfig) {
|
||||
state.walletConfig = config
|
||||
state.onboardingState.walletConfig = config
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ struct RootReducer: ReducerProtocol {
|
|||
case phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
|
||||
case sandbox(SandboxReducer.Action)
|
||||
case welcome(WelcomeReducer.Action)
|
||||
case debug(DebugAction)
|
||||
}
|
||||
|
||||
@Dependency(\.crashReporter) var crashReporter
|
||||
|
|
|
@ -83,28 +83,75 @@ struct RootView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct FeatureFlagWrapper: Identifiable, Equatable, Comparable {
|
||||
let name: FeatureFlag
|
||||
let isEnabled: Bool
|
||||
var id: String { name.rawValue }
|
||||
|
||||
static func < (lhs: FeatureFlagWrapper, rhs: FeatureFlagWrapper) -> Bool {
|
||||
lhs.name.rawValue < rhs.name.rawValue
|
||||
}
|
||||
|
||||
static func == (lhs: FeatureFlagWrapper, rhs: FeatureFlagWrapper) -> Bool {
|
||||
lhs.name.rawValue == rhs.name.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
private extension RootView {
|
||||
@ViewBuilder func debugView(_ viewStore: RootViewStore) -> some View {
|
||||
List {
|
||||
Section(header: Text("Navigation Stack Destinations")) {
|
||||
Button("Go To Sandbox (navigation proof)") {
|
||||
viewStore.goToDestination(.sandbox)
|
||||
VStack(alignment: .leading) {
|
||||
Button("Back") {
|
||||
viewStore.goToDestination(.home)
|
||||
}
|
||||
.navigationButtonStyle
|
||||
.frame(width: 75, height: 40, alignment: .leading)
|
||||
.padding()
|
||||
|
||||
List {
|
||||
Section(header: Text("Navigation Stack Destinations")) {
|
||||
Button("Go To Sandbox (navigation proof)") {
|
||||
viewStore.goToDestination(.sandbox)
|
||||
}
|
||||
|
||||
Button("Go To Onboarding") {
|
||||
viewStore.goToDestination(.onboarding)
|
||||
}
|
||||
|
||||
Button("Go To Phrase Validation Demo") {
|
||||
viewStore.goToDestination(.phraseValidation)
|
||||
}
|
||||
|
||||
Button("Restart the app") {
|
||||
viewStore.goToDestination(.welcome)
|
||||
}
|
||||
|
||||
Button("[Be careful] Nuke Wallet") {
|
||||
viewStore.send(.initialization(.nukeWallet))
|
||||
}
|
||||
}
|
||||
|
||||
Button("Go To Onboarding") {
|
||||
viewStore.goToDestination(.onboarding)
|
||||
}
|
||||
|
||||
Button("Go To Phrase Validation Demo") {
|
||||
viewStore.goToDestination(.phraseValidation)
|
||||
}
|
||||
|
||||
Button("Restart the app") {
|
||||
viewStore.goToDestination(.welcome)
|
||||
}
|
||||
|
||||
Button("[Be careful] Nuke Wallet") {
|
||||
viewStore.send(.initialization(.nukeWallet))
|
||||
|
||||
Section(header: Text("Feature flags")) {
|
||||
let flags = viewStore.state.walletConfig.flags
|
||||
.map { FeatureFlagWrapper(name: $0.key, isEnabled: $0.value) }
|
||||
.sorted()
|
||||
|
||||
ForEach(flags) { flag in
|
||||
HStack {
|
||||
Toggle(
|
||||
isOn: Binding(
|
||||
get: { flag.isEnabled },
|
||||
set: { _ in
|
||||
viewStore.send(.debug(.updateFlag(flag.name, flag.isEnabled)))
|
||||
}
|
||||
),
|
||||
label: {
|
||||
Text(flag.name.rawValue)
|
||||
.foregroundColor(flag.isEnabled ? .green : .red)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,10 @@ struct WalletConfig: Equatable {
|
|||
}
|
||||
|
||||
static var `default`: WalletConfig = {
|
||||
let defaultSettings = FeatureFlag.allCases.map { ($0, $0.enabledByDefault) }
|
||||
let defaultSettings = FeatureFlag.allCases
|
||||
.filter { $0 != .testFlag1 && $0 != .testFlag2 }
|
||||
.map { ($0, $0.enabledByDefault) }
|
||||
|
||||
return WalletConfig(flags: Dictionary(uniqueKeysWithValues: defaultSettings))
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -11,14 +11,15 @@ import XCTest
|
|||
class WalletConfigProviderTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
UserDefaultsWalletConfigStorage().clearAll()
|
||||
}
|
||||
|
||||
func testLoadFlagsFromProvider() async {
|
||||
func testTestFlagsAreDisabledByDefault() {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
}
|
||||
|
||||
func testLoadFlagsFromProvider() async {
|
||||
let provider = WalletConfigSourceProviderMock() {
|
||||
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
||||
}
|
||||
|
@ -31,9 +32,6 @@ class WalletConfigProviderTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testLoadFlagsFromCache() async {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
|
||||
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
||||
let cache = WalletConfigProviderCacheMock(cachedFlags: [.testFlag1: false, .testFlag2: true])
|
||||
|
||||
|
@ -45,23 +43,17 @@ class WalletConfigProviderTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testLoadDefaultFlags() async {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
|
||||
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
||||
let cache = WalletConfigProviderCacheMock(cachedFlags: [:])
|
||||
|
||||
let manager = WalletConfigProvider(configSourceProvider: provider, cache: cache)
|
||||
let configuration = await manager.load()
|
||||
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
XCTAssertFalse(configuration.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(configuration.isEnabled(.testFlag2))
|
||||
}
|
||||
|
||||
func testAllTheFlagsAreAlwaysReturned() async {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
|
||||
let provider = WalletConfigSourceProviderMock() {
|
||||
return WalletConfig(flags: [.testFlag1: true])
|
||||
}
|
||||
|
@ -74,9 +66,6 @@ class WalletConfigProviderTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testProvidedFlagsAreCached() async {
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||
|
||||
let provider = WalletConfigSourceProviderMock() {
|
||||
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue