Skip to content
Snippets Groups Projects
Commit fccbe606 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Update LandingFeature

parent dd9c6b49
No related branches found
No related tags found
2 merge requests!102Release 1.0.0,!19Update example app
......@@ -140,6 +140,10 @@ let package = Package(
name: "ElixxirDAppsSDK",
package: "elixxir-dapps-sdk-swift"
),
.product(
name: "XCTestDynamicOverlay",
package: "xctest-dynamic-overlay"
),
],
swiftSettings: swiftSettings
),
......
......@@ -2,58 +2,59 @@ import Combine
import ComposableArchitecture
import ElixxirDAppsSDK
import ErrorFeature
import XCTestDynamicOverlay
public struct LandingState: Equatable {
public init(
id: UUID,
hasStoredClient: Bool = false,
isMakingClient: Bool = false,
isRemovingClient: Bool = false,
hasStoredCmix: Bool = false,
isMakingCmix: Bool = false,
isRemovingCmix: Bool = false,
error: ErrorState? = nil
) {
self.id = id
self.hasStoredClient = hasStoredClient
self.isMakingClient = isMakingClient
self.isRemovingClient = isRemovingClient
self.hasStoredCmix = hasStoredCmix
self.isMakingCmix = isMakingCmix
self.isRemovingCmix = isRemovingCmix
self.error = error
}
var id: UUID
var hasStoredClient: Bool
var isMakingClient: Bool
var isRemovingClient: Bool
var hasStoredCmix: Bool
var isMakingCmix: Bool
var isRemovingCmix: Bool
var error: ErrorState?
}
public enum LandingAction: Equatable {
case viewDidLoad
case makeClient
case didMakeClient
case didFailMakingClient(NSError)
case removeStoredClient
case didRemoveStoredClient
case didFailRemovingStoredClient(NSError)
case makeCmix
case didMakeCmix
case didFailMakingCmix(NSError)
case removeStoredCmix
case didRemoveStoredCmix
case didFailRemovingStoredCmix(NSError)
case didDismissError
case error(ErrorAction)
}
public struct LandingEnvironment {
public init(
clientStorage: ClientStorage,
setClient: @escaping (Client) -> Void,
cmixManager: CmixManager,
setCmix: @escaping (Cmix) -> Void,
bgScheduler: AnySchedulerOf<DispatchQueue>,
mainScheduler: AnySchedulerOf<DispatchQueue>,
error: ErrorEnvironment
) {
self.clientStorage = clientStorage
self.setClient = setClient
self.cmixManager = cmixManager
self.setCmix = setCmix
self.bgScheduler = bgScheduler
self.mainScheduler = mainScheduler
self.error = error
}
public var clientStorage: ClientStorage
public var setClient: (Client) -> Void
public var cmixManager: CmixManager
public var setCmix: (Cmix) -> Void
public var bgScheduler: AnySchedulerOf<DispatchQueue>
public var mainScheduler: AnySchedulerOf<DispatchQueue>
public var error: ErrorEnvironment
......@@ -63,60 +64,60 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm
{ state, action, env in
switch action {
case .viewDidLoad:
state.hasStoredClient = env.clientStorage.hasStoredClient()
state.hasStoredCmix = env.cmixManager.hasStorage()
return .none
case .makeClient:
state.isMakingClient = true
case .makeCmix:
state.isMakingCmix = true
return Effect.future { fulfill in
do {
if env.clientStorage.hasStoredClient() {
env.setClient(try env.clientStorage.loadClient())
if env.cmixManager.hasStorage() {
env.setCmix(try env.cmixManager.load())
} else {
env.setClient(try env.clientStorage.createClient())
env.setCmix(try env.cmixManager.create())
}
fulfill(.success(.didMakeClient))
fulfill(.success(.didMakeCmix))
} catch {
fulfill(.success(.didFailMakingClient(error as NSError)))
fulfill(.success(.didFailMakingCmix(error as NSError)))
}
}
.subscribe(on: env.bgScheduler)
.receive(on: env.mainScheduler)
.eraseToEffect()
case .didMakeClient:
state.isMakingClient = false
state.hasStoredClient = env.clientStorage.hasStoredClient()
case .didMakeCmix:
state.isMakingCmix = false
state.hasStoredCmix = env.cmixManager.hasStorage()
return .none
case .didFailMakingClient(let error):
state.isMakingClient = false
state.hasStoredClient = env.clientStorage.hasStoredClient()
case .didFailMakingCmix(let error):
state.isMakingCmix = false
state.hasStoredCmix = env.cmixManager.hasStorage()
state.error = ErrorState(error: error)
return .none
case .removeStoredClient:
state.isRemovingClient = true
case .removeStoredCmix:
state.isRemovingCmix = true
return Effect.future { fulfill in
do {
try env.clientStorage.removeClient()
fulfill(.success(.didRemoveStoredClient))
try env.cmixManager.remove()
fulfill(.success(.didRemoveStoredCmix))
} catch {
fulfill(.success(.didFailRemovingStoredClient(error as NSError)))
fulfill(.success(.didFailRemovingStoredCmix(error as NSError)))
}
}
.subscribe(on: env.bgScheduler)
.receive(on: env.mainScheduler)
.eraseToEffect()
case .didRemoveStoredClient:
state.isRemovingClient = false
state.hasStoredClient = env.clientStorage.hasStoredClient()
case .didRemoveStoredCmix:
state.isRemovingCmix = false
state.hasStoredCmix = env.cmixManager.hasStorage()
return .none
case .didFailRemovingStoredClient(let error):
state.isRemovingClient = false
state.hasStoredClient = env.clientStorage.hasStoredClient()
case .didFailRemovingStoredCmix(let error):
state.isRemovingCmix = false
state.hasStoredCmix = env.cmixManager.hasStorage()
state.error = ErrorState(error: error)
return .none
......@@ -133,14 +134,12 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm
environment: \.error
)
#if DEBUG
extension LandingEnvironment {
public static let failing = LandingEnvironment(
clientStorage: .failing,
setClient: { _ in fatalError() },
bgScheduler: .failing,
mainScheduler: .failing,
error: .failing
public static let unimplemented = LandingEnvironment(
cmixManager: .unimplemented,
setCmix: XCTUnimplemented("\(Self.self).setCmix"),
bgScheduler: .unimplemented,
mainScheduler: .unimplemented,
error: .unimplemented
)
}
#endif
......@@ -11,19 +11,19 @@ public struct LandingView: View {
let store: Store<LandingState, LandingAction>
struct ViewState: Equatable {
let hasStoredClient: Bool
let isMakingClient: Bool
let isRemovingClient: Bool
let hasStoredCmix: Bool
let isMakingCmix: Bool
let isRemovingCmix: Bool
init(state: LandingState) {
hasStoredClient = state.hasStoredClient
isMakingClient = state.isMakingClient
isRemovingClient = state.isRemovingClient
hasStoredCmix = state.hasStoredCmix
isMakingCmix = state.isMakingCmix
isRemovingCmix = state.isRemovingCmix
}
var isLoading: Bool {
isMakingClient ||
isRemovingClient
isMakingCmix ||
isRemovingCmix
}
}
......@@ -31,25 +31,25 @@ public struct LandingView: View {
WithViewStore(store.scope(state: ViewState.init)) { viewStore in
Form {
Button {
viewStore.send(.makeClient)
viewStore.send(.makeCmix)
} label: {
HStack {
Text(viewStore.hasStoredClient ? "Load stored client" : "Create new client")
Text(viewStore.hasStoredCmix ? "Load stored cMix" : "Create new cMix")
Spacer()
if viewStore.isMakingClient {
if viewStore.isMakingCmix {
ProgressView()
}
}
}
if viewStore.hasStoredClient {
if viewStore.hasStoredCmix {
Button(role: .destructive) {
viewStore.send(.removeStoredClient)
viewStore.send(.removeStoredCmix)
} label: {
HStack {
Text("Remove stored client")
Text("Remove stored cMix")
Spacer()
if viewStore.isRemovingClient {
if viewStore.isRemovingCmix {
ProgressView()
}
}
......
......@@ -5,183 +5,177 @@ import XCTest
final class LandingFeatureTests: XCTestCase {
func testViewDidLoad() throws {
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { true }
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.environment.cmixManager.hasStorage.run = { true }
store.send(.viewDidLoad) {
$0.hasStoredClient = true
$0.hasStoredCmix = true
}
}
func testCreateClient() {
var hasStoredClient = false
var didSetClient = false
func testCreateCmix() {
var hasStoredCmix = false
var didSetCmix = false
let bgScheduler = DispatchQueue.test
let mainScheduler = DispatchQueue.test
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { hasStoredClient }
env.clientStorage.createClient = { .failing }
env.setClient = { _ in didSetClient = true }
env.bgScheduler = bgScheduler.eraseToAnyScheduler()
env.mainScheduler = mainScheduler.eraseToAnyScheduler()
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.send(.makeClient) {
$0.isMakingClient = true
store.environment.cmixManager.hasStorage.run = { hasStoredCmix }
store.environment.cmixManager.create.run = { .unimplemented }
store.environment.setCmix = { _ in didSetCmix = true }
store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler()
store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler()
store.send(.makeCmix) {
$0.isMakingCmix = true
}
bgScheduler.advance()
XCTAssertTrue(didSetClient)
XCTAssertTrue(didSetCmix)
hasStoredClient = true
hasStoredCmix = true
mainScheduler.advance()
store.receive(.didMakeClient) {
$0.isMakingClient = false
$0.hasStoredClient = true
store.receive(.didMakeCmix) {
$0.isMakingCmix = false
$0.hasStoredCmix = true
}
}
func testLoadStoredClient() {
var didSetClient = false
func testLoadStoredCmix() {
var didSetCmix = false
let bgScheduler = DispatchQueue.test
let mainScheduler = DispatchQueue.test
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { true }
env.clientStorage.loadClient = { .failing }
env.setClient = { _ in didSetClient = true }
env.bgScheduler = bgScheduler.eraseToAnyScheduler()
env.mainScheduler = mainScheduler.eraseToAnyScheduler()
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.send(.makeClient) {
$0.isMakingClient = true
store.environment.cmixManager.hasStorage.run = { true }
store.environment.cmixManager.load.run = { .unimplemented }
store.environment.setCmix = { _ in didSetCmix = true }
store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler()
store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler()
store.send(.makeCmix) {
$0.isMakingCmix = true
}
bgScheduler.advance()
XCTAssertTrue(didSetClient)
XCTAssertTrue(didSetCmix)
mainScheduler.advance()
store.receive(.didMakeClient) {
$0.isMakingClient = false
$0.hasStoredClient = true
store.receive(.didMakeCmix) {
$0.isMakingCmix = false
$0.hasStoredCmix = true
}
}
func testMakeClientFailure() {
func testMakeCmixFailure() {
let error = NSError(domain: "test", code: 1234)
let bgScheduler = DispatchQueue.test
let mainScheduler = DispatchQueue.test
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { false }
env.clientStorage.createClient = { throw error }
env.bgScheduler = bgScheduler.eraseToAnyScheduler()
env.mainScheduler = mainScheduler.eraseToAnyScheduler()
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.send(.makeClient) {
$0.isMakingClient = true
store.environment.cmixManager.hasStorage.run = { false }
store.environment.cmixManager.create.run = { throw error }
store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler()
store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler()
store.send(.makeCmix) {
$0.isMakingCmix = true
}
bgScheduler.advance()
mainScheduler.advance()
store.receive(.didFailMakingClient(error)) {
$0.isMakingClient = false
$0.hasStoredClient = false
store.receive(.didFailMakingCmix(error)) {
$0.isMakingCmix = false
$0.hasStoredCmix = false
$0.error = ErrorState(error: error)
}
}
func testRemoveStoredClient() {
var hasStoredClient = true
var didRemoveClient = false
func testRemoveStoredCmix() {
var hasStoredCmix = true
var didRemoveCmix = false
let bgScheduler = DispatchQueue.test
let mainScheduler = DispatchQueue.test
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { hasStoredClient }
env.clientStorage.removeClient = { didRemoveClient = true }
env.bgScheduler = bgScheduler.eraseToAnyScheduler()
env.mainScheduler = mainScheduler.eraseToAnyScheduler()
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.send(.removeStoredClient) {
$0.isRemovingClient = true
store.environment.cmixManager.hasStorage.run = { hasStoredCmix }
store.environment.cmixManager.remove.run = { didRemoveCmix = true }
store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler()
store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler()
store.send(.removeStoredCmix) {
$0.isRemovingCmix = true
}
bgScheduler.advance()
XCTAssertTrue(didRemoveClient)
XCTAssertTrue(didRemoveCmix)
hasStoredClient = false
hasStoredCmix = false
mainScheduler.advance()
store.receive(.didRemoveStoredClient) {
$0.isRemovingClient = false
$0.hasStoredClient = false
store.receive(.didRemoveStoredCmix) {
$0.isRemovingCmix = false
$0.hasStoredCmix = false
}
}
func testRemoveStoredClientFailure() {
func testRemoveStoredCmixFailure() {
let error = NSError(domain: "test", code: 1234)
let bgScheduler = DispatchQueue.test
let mainScheduler = DispatchQueue.test
var env = LandingEnvironment.failing
env.clientStorage.hasStoredClient = { true }
env.clientStorage.removeClient = { throw error }
env.bgScheduler = bgScheduler.eraseToAnyScheduler()
env.mainScheduler = mainScheduler.eraseToAnyScheduler()
let store = TestStore(
initialState: LandingState(id: UUID()),
reducer: landingReducer,
environment: env
environment: .unimplemented
)
store.send(.removeStoredClient) {
$0.isRemovingClient = true
store.environment.cmixManager.hasStorage.run = { true }
store.environment.cmixManager.remove.run = { throw error }
store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler()
store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler()
store.send(.removeStoredCmix) {
$0.isRemovingCmix = true
}
bgScheduler.advance()
mainScheduler.advance()
store.receive(.didFailRemovingStoredClient(error)) {
$0.isRemovingClient = false
$0.hasStoredClient = true
store.receive(.didFailRemovingStoredCmix(error)) {
$0.isRemovingCmix = false
$0.hasStoredCmix = true
$0.error = ErrorState(error: error)
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment