parent
56e1364659
commit
148ca941f1
|
@ -17,4 +17,5 @@ extension DependencyValues {
|
||||||
|
|
||||||
struct WalletConfigProviderClient {
|
struct WalletConfigProviderClient {
|
||||||
let load: () async -> WalletConfig
|
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 {
|
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 {
|
extension WalletConfigProviderClient: TestDependencyKey {
|
||||||
static let testValue = Self(
|
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 {
|
extension WalletConfigProviderClient {
|
||||||
static let noOp = Self(
|
static let noOp = Self(
|
||||||
load: { WalletConfig.default }
|
load: { WalletConfig.default },
|
||||||
|
update: { _, _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ extension RootReducer {
|
||||||
}
|
}
|
||||||
return EffectTask(value: .destination(.deeplink(url)))
|
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
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,11 @@ extension RootReducer {
|
||||||
case walletConfigChanged(WalletConfig)
|
case walletConfigChanged(WalletConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DebugAction: Equatable {
|
||||||
|
case updateFlag(FeatureFlag, Bool)
|
||||||
|
case flagUpdated(WalletConfig)
|
||||||
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||||
func initializationReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
|
func initializationReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
|
||||||
Reduce { state, action in
|
Reduce { state, action in
|
||||||
|
@ -45,8 +50,7 @@ extension RootReducer {
|
||||||
}
|
}
|
||||||
|
|
||||||
case .initialization(.walletConfigChanged(let walletConfig)):
|
case .initialization(.walletConfigChanged(let walletConfig)):
|
||||||
state.walletConfig = walletConfig
|
updateStateAfterConfigUpdate(state: &state, config: walletConfig)
|
||||||
state.onboardingState.walletConfig = walletConfig
|
|
||||||
return EffectTask(value: .initialization(.initialSetups))
|
return EffectTask(value: .initialization(.initialSetups))
|
||||||
|
|
||||||
case .initialization(.initialSetups):
|
case .initialization(.initialSetups):
|
||||||
|
@ -227,7 +231,23 @@ extension RootReducer {
|
||||||
!userStoredPreferences.isUserOptedOutOfCrashReporting()
|
!userStoredPreferences.isUserOptedOutOfCrashReporting()
|
||||||
)
|
)
|
||||||
return .none
|
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 phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
|
||||||
case sandbox(SandboxReducer.Action)
|
case sandbox(SandboxReducer.Action)
|
||||||
case welcome(WelcomeReducer.Action)
|
case welcome(WelcomeReducer.Action)
|
||||||
|
case debug(DebugAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dependency(\.crashReporter) var crashReporter
|
@Dependency(\.crashReporter) var crashReporter
|
||||||
|
|
|
@ -83,8 +83,30 @@ 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 {
|
private extension RootView {
|
||||||
@ViewBuilder func debugView(_ viewStore: RootViewStore) -> some View {
|
@ViewBuilder func debugView(_ viewStore: RootViewStore) -> some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Button("Back") {
|
||||||
|
viewStore.goToDestination(.home)
|
||||||
|
}
|
||||||
|
.navigationButtonStyle
|
||||||
|
.frame(width: 75, height: 40, alignment: .leading)
|
||||||
|
.padding()
|
||||||
|
|
||||||
List {
|
List {
|
||||||
Section(header: Text("Navigation Stack Destinations")) {
|
Section(header: Text("Navigation Stack Destinations")) {
|
||||||
Button("Go To Sandbox (navigation proof)") {
|
Button("Go To Sandbox (navigation proof)") {
|
||||||
|
@ -107,6 +129,31 @@ private extension RootView {
|
||||||
viewStore.send(.initialization(.nukeWallet))
|
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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Startup")
|
.navigationBarTitle("Startup")
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,10 @@ struct WalletConfig: Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
static var `default`: WalletConfig = {
|
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))
|
return WalletConfig(flags: Dictionary(uniqueKeysWithValues: defaultSettings))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,15 @@ import XCTest
|
||||||
class WalletConfigProviderTests: XCTestCase {
|
class WalletConfigProviderTests: XCTestCase {
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|
||||||
UserDefaultsWalletConfigStorage().clearAll()
|
UserDefaultsWalletConfigStorage().clearAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadFlagsFromProvider() async {
|
func testTestFlagsAreDisabledByDefault() {
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLoadFlagsFromProvider() async {
|
||||||
let provider = WalletConfigSourceProviderMock() {
|
let provider = WalletConfigSourceProviderMock() {
|
||||||
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
||||||
}
|
}
|
||||||
|
@ -31,9 +32,6 @@ class WalletConfigProviderTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadFlagsFromCache() async {
|
func testLoadFlagsFromCache() async {
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
|
||||||
|
|
||||||
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
||||||
let cache = WalletConfigProviderCacheMock(cachedFlags: [.testFlag1: false, .testFlag2: true])
|
let cache = WalletConfigProviderCacheMock(cachedFlags: [.testFlag1: false, .testFlag2: true])
|
||||||
|
|
||||||
|
@ -45,23 +43,17 @@ class WalletConfigProviderTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadDefaultFlags() async {
|
func testLoadDefaultFlags() async {
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
|
||||||
|
|
||||||
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
let provider = WalletConfigSourceProviderMock() { throw NSError(domain: "whatever", code: 21) }
|
||||||
let cache = WalletConfigProviderCacheMock(cachedFlags: [:])
|
let cache = WalletConfigProviderCacheMock(cachedFlags: [:])
|
||||||
|
|
||||||
let manager = WalletConfigProvider(configSourceProvider: provider, cache: cache)
|
let manager = WalletConfigProvider(configSourceProvider: provider, cache: cache)
|
||||||
let configuration = await manager.load()
|
let configuration = await manager.load()
|
||||||
|
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
XCTAssertFalse(configuration.isEnabled(.testFlag1))
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
XCTAssertFalse(configuration.isEnabled(.testFlag2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAllTheFlagsAreAlwaysReturned() async {
|
func testAllTheFlagsAreAlwaysReturned() async {
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
|
||||||
|
|
||||||
let provider = WalletConfigSourceProviderMock() {
|
let provider = WalletConfigSourceProviderMock() {
|
||||||
return WalletConfig(flags: [.testFlag1: true])
|
return WalletConfig(flags: [.testFlag1: true])
|
||||||
}
|
}
|
||||||
|
@ -74,9 +66,6 @@ class WalletConfigProviderTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testProvidedFlagsAreCached() async {
|
func testProvidedFlagsAreCached() async {
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag1))
|
|
||||||
XCTAssertFalse(WalletConfig.default.isEnabled(.testFlag2))
|
|
||||||
|
|
||||||
let provider = WalletConfigSourceProviderMock() {
|
let provider = WalletConfigSourceProviderMock() {
|
||||||
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
return WalletConfig(flags: [.testFlag1: true, .testFlag2: false])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue