mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Silence test warnings * wip * wip * wip * update a bunch of docs * wip * wip * fix * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Kill integration tests for now * wip * wip * wip * wip * updating docs for @Reducer macro * replaced more Reducer protocols with @Reducer * Fixed some broken docc references * wip * Some @Reducer docs * more docs * convert some old styles to new style * wip * wip * wip * wip * wip * wip * wip * bump * update tutorials to use body * update tutorials to use DML on destination state enum * Add diagnostic * wip * updated a few more tests * wip * wip * Add another gotcha * wip * wip * wip * fixes * wip * wip * wip * wip * wip * fix * wip * remove for now * wip * wip * updated some docs * migration guides * more migration guide * fix ci * fix * soft deprecate all apis using AnyCasePath * wip * Fix * fix tests * swift-format 509 compatibility * wip * wip * Update Sources/ComposableArchitecture/Macros.swift Co-authored-by: Mateusz Bąk <bakmatthew@icloud.com> * wip * wip * update optional state case study * remove initializer * Don't use @State for BasicsView integration demo * fix tests * remove reduce diagnostics for now * diagnose error not warning * Update Sources/ComposableArchitecture/Macros.swift Co-authored-by: Jesse Tipton <jesse@jessetipton.com> * wip * move integration tests to cron * Revert "move integration tests to cron" This reverts commitf9bdf2f04b. * disable flakey tests on CI * wip * wip * Revert "Revert "move integration tests to cron"" This reverts commit66aafa7327. * fix * wip * fix --------- Co-authored-by: Brandon Williams <mbrandonw@hey.com> Co-authored-by: Mateusz Bąk <bakmatthew@icloud.com> Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Co-authored-by: Jesse Tipton <jesse@jessetipton.com>
224 lines
6.6 KiB
Swift
224 lines
6.6 KiB
Swift
import Combine
|
|
@_spi(Internals) import ComposableArchitecture
|
|
import SwiftUI
|
|
import XCTest
|
|
|
|
@MainActor
|
|
@available(*, deprecated, message: "TODO: Update to use case pathable syntax with Swift 5.9")
|
|
final class BindableStoreTests: XCTestCase {
|
|
func testBindableStore() {
|
|
struct BindableReducer: Reducer {
|
|
struct State: Equatable {
|
|
@BindingState var something: Int
|
|
}
|
|
|
|
enum Action: BindableAction {
|
|
case binding(BindingAction<State>)
|
|
}
|
|
|
|
var body: some ReducerOf<Self> {
|
|
BindingReducer()
|
|
}
|
|
}
|
|
|
|
struct SomeView_BindableViewState: View {
|
|
let store: StoreOf<BindableReducer>
|
|
|
|
struct ViewState: Equatable {
|
|
@BindingViewState var something: Int
|
|
}
|
|
|
|
var body: some View {
|
|
WithViewStore(store, observe: { ViewState(something: $0.$something) }) { viewStore in
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SomeView_BindableViewState_Observed: View {
|
|
let store: StoreOf<BindableReducer>
|
|
@ObservedObject var viewStore: ViewStore<ViewState, BindableReducer.Action>
|
|
|
|
struct ViewState: Equatable {
|
|
@BindingViewState var something: Int
|
|
}
|
|
|
|
init(store: StoreOf<BindableReducer>) {
|
|
self.store = store
|
|
self.viewStore = ViewStore(store, observe: { ViewState(something: $0.$something) })
|
|
}
|
|
|
|
var body: some View {
|
|
EmptyView()
|
|
}
|
|
}
|
|
|
|
struct SomeView_NoBindableViewState: View {
|
|
let store: StoreOf<BindableReducer>
|
|
|
|
struct ViewState: Equatable {}
|
|
|
|
var body: some View {
|
|
WithViewStore(store, observe: { _ in ViewState() }) { viewStore in
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SomeView_NoBindableViewState_Observed: View {
|
|
let store: StoreOf<BindableReducer>
|
|
@ObservedObject var viewStore: ViewStore<ViewState, BindableReducer.Action>
|
|
|
|
struct ViewState: Equatable {}
|
|
|
|
init(store: StoreOf<BindableReducer>) {
|
|
self.store = store
|
|
self.viewStore = ViewStore(store, observe: { _ in ViewState() })
|
|
}
|
|
|
|
var body: some View {
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
func testTestStoreBindings() async {
|
|
struct LoginFeature: Reducer {
|
|
struct State: Equatable {
|
|
@BindingState var email = ""
|
|
public var isFormValid = false
|
|
public var isRequestInFlight = false
|
|
@BindingState var password = ""
|
|
}
|
|
enum Action: Equatable, BindableAction {
|
|
case binding(BindingAction<State>)
|
|
case loginButtonTapped
|
|
}
|
|
var body: some ReducerOf<Self> {
|
|
BindingReducer()
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .binding:
|
|
state.isFormValid = !state.email.isEmpty && !state.password.isEmpty
|
|
return .none
|
|
case .loginButtonTapped:
|
|
state.isRequestInFlight = true
|
|
return .none // NB: Login request
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LoginViewState: Equatable {
|
|
@BindingViewState var email: String
|
|
var isFormDisabled: Bool
|
|
var isLoginButtonDisabled: Bool
|
|
@BindingViewState var password: String
|
|
|
|
init(_ store: BindingViewStore<LoginFeature.State>) {
|
|
self._email = store.$email
|
|
self.isFormDisabled = store.isRequestInFlight
|
|
self.isLoginButtonDisabled = !store.isFormValid || store.isRequestInFlight
|
|
self._password = store.$password
|
|
}
|
|
}
|
|
|
|
let store = TestStore(initialState: LoginFeature.State()) {
|
|
LoginFeature()
|
|
}
|
|
await store.send(.set(\.$email, "blob@pointfree.co")) {
|
|
$0.email = "blob@pointfree.co"
|
|
}
|
|
XCTAssertFalse(LoginViewState(store.bindings).isFormDisabled)
|
|
XCTAssertTrue(LoginViewState(store.bindings).isLoginButtonDisabled)
|
|
await store.send(.set(\.$password, "blob123")) {
|
|
$0.password = "blob123"
|
|
$0.isFormValid = true
|
|
}
|
|
XCTAssertFalse(LoginViewState(store.bindings).isFormDisabled)
|
|
XCTAssertFalse(LoginViewState(store.bindings).isLoginButtonDisabled)
|
|
await store.send(.loginButtonTapped) {
|
|
$0.isRequestInFlight = true
|
|
}
|
|
XCTAssertTrue(LoginViewState(store.bindings).isFormDisabled)
|
|
XCTAssertTrue(LoginViewState(store.bindings).isLoginButtonDisabled)
|
|
}
|
|
|
|
func testTestStoreBindings_ViewAction() async {
|
|
struct LoginFeature: Reducer {
|
|
struct State: Equatable {
|
|
@BindingState var email = ""
|
|
public var isFormValid = false
|
|
public var isRequestInFlight = false
|
|
@BindingState var password = ""
|
|
}
|
|
enum Action: Equatable {
|
|
case view(View)
|
|
enum View: Equatable, BindableAction {
|
|
case binding(BindingAction<State>)
|
|
case loginButtonTapped
|
|
}
|
|
}
|
|
var body: some ReducerOf<Self> {
|
|
BindingReducer(action: /Action.view)
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .view(.binding):
|
|
state.isFormValid = !state.email.isEmpty && !state.password.isEmpty
|
|
return .none
|
|
case .view(.loginButtonTapped):
|
|
state.isRequestInFlight = true
|
|
return .none // NB: Login request
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LoginViewState: Equatable {
|
|
@BindingViewState var email: String
|
|
var isFormDisabled: Bool
|
|
var isLoginButtonDisabled: Bool
|
|
@BindingViewState var password: String
|
|
|
|
init(_ store: BindingViewStore<LoginFeature.State>) {
|
|
self._email = store.$email
|
|
self.isFormDisabled = store.isRequestInFlight
|
|
self.isLoginButtonDisabled = !store.isFormValid || store.isRequestInFlight
|
|
self._password = store.$password
|
|
}
|
|
}
|
|
|
|
let store = TestStore(initialState: LoginFeature.State()) {
|
|
LoginFeature()
|
|
}
|
|
await store.send(.view(.set(\.$email, "blob@pointfree.co"))) {
|
|
$0.email = "blob@pointfree.co"
|
|
}
|
|
XCTAssertFalse(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isFormDisabled
|
|
)
|
|
XCTAssertTrue(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isLoginButtonDisabled
|
|
)
|
|
await store.send(.view(.set(\.$password, "blob123"))) {
|
|
$0.password = "blob123"
|
|
$0.isFormValid = true
|
|
}
|
|
XCTAssertFalse(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isFormDisabled
|
|
)
|
|
XCTAssertFalse(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isLoginButtonDisabled
|
|
)
|
|
await store.send(.view(.loginButtonTapped)) {
|
|
$0.isRequestInFlight = true
|
|
}
|
|
XCTAssertTrue(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isFormDisabled
|
|
)
|
|
XCTAssertTrue(
|
|
LoginViewState(store.bindings(action: /LoginFeature.Action.view)).isLoginButtonDisabled
|
|
)
|
|
}
|
|
}
|