Skip to content

Commit b4a3d04

Browse files
scogeostephencelis
andauthored
Add overloads of TestStore.send that accept CaseKeyPaths (#2681)
Co-authored-by: Stephen Celis <[email protected]>
1 parent 3346f12 commit b4a3d04

File tree

2 files changed

+124
-1
lines changed

2 files changed

+124
-1
lines changed

Sources/ComposableArchitecture/TestStore.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,84 @@ extension TestStore where State: Equatable {
19341934
}
19351935
}
19361936

1937+
extension TestStore where State: Equatable {
1938+
/// Sends an action to the store and asserts when state changes.
1939+
///
1940+
/// This method is similar to ``send(_:assert:file:line:)-2co21``, except it allows
1941+
/// you to specify a `CaseKeyPath` of an action with no associated value to be sent to the store.
1942+
///
1943+
/// It can be useful when sending nested action. For example::
1944+
///
1945+
/// ```swift
1946+
/// await store.send(.view(.tap))
1947+
/// ```
1948+
///
1949+
/// Can be simplified to:
1950+
///
1951+
/// ```swift
1952+
/// await store.send(\.view.tap)
1953+
/// ```
1954+
///
1955+
/// - Parameters:
1956+
/// - action: A `CaseKeyPath` to an action.
1957+
/// - updateStateToExpectedResult: A closure that asserts state changed by sending the action to
1958+
/// the store. The mutable state sent to this closure must be modified to match the state of
1959+
/// the store after processing the given action. Do not provide a closure if no change is
1960+
/// expected.
1961+
/// - Returns: A ``TestStoreTask`` that represents the lifecycle of the effect executed when
1962+
/// sending the action.
1963+
@MainActor
1964+
@discardableResult
1965+
@_disfavoredOverload
1966+
public func send(
1967+
_ action: CaseKeyPath<Action, Void>,
1968+
assert updateStateToExpectedResult: ((_ state: inout State) throws -> Void)? = nil,
1969+
file: StaticString = #file,
1970+
line: UInt = #line
1971+
) async -> TestStoreTask {
1972+
await self.send(action(), assert: updateStateToExpectedResult, file: file, line: line)
1973+
}
1974+
1975+
/// Sends an action to the store and asserts when state changes.
1976+
///
1977+
/// This method is similar to ``send(_:assert:file:line:)-1oopl``, except it allows
1978+
/// you to specify a value for the associated value of the action.
1979+
///
1980+
/// It can be useful when sending nested action. For example::
1981+
///
1982+
/// ```swift
1983+
/// await store.send(.view(.delete([19, 23]))
1984+
/// ```
1985+
///
1986+
/// Can be simplified to:
1987+
///
1988+
/// ```swift
1989+
/// await store.send(\.view.delete, [19, 23])
1990+
/// ```
1991+
///
1992+
/// - Parameters:
1993+
/// - action: A `CaseKeyPath` to an action with an associated value.
1994+
/// - value: A value for the associated value specified in `action`.
1995+
/// - updateStateToExpectedResult: A closure that asserts state changed by sending the action to
1996+
/// the store. The mutable state sent to this closure must be modified to match the state of
1997+
/// the store after processing the given action. Do not provide a closure if no change is
1998+
/// expected.
1999+
/// - Returns: A ``TestStoreTask`` that represents the lifecycle of the effect executed when
2000+
/// sending the action.
2001+
@MainActor
2002+
@discardableResult
2003+
@_disfavoredOverload
2004+
public func send<Value>(
2005+
_ action: CaseKeyPath<Action, Value>,
2006+
_ value: Value,
2007+
assert updateStateToExpectedResult: ((_ state: inout State) throws -> Void)? = nil,
2008+
file: StaticString = #file,
2009+
line: UInt = #line
2010+
) async -> TestStoreTask {
2011+
await self.send(action(value), assert: updateStateToExpectedResult, file: file, line: line)
2012+
}
2013+
}
2014+
19372015
extension TestStore {
19382016
/// Clears the queue of received actions from effects.
19392017
///

Tests/ComposableArchitectureTests/TestStoreTests.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@
536536
return .send(.delegate(.success(42)))
537537
case .delegate:
538538
return .none
539+
case .view:
540+
return .none
539541
}
540542
}
541543
}
@@ -558,6 +560,43 @@
558560
await store.send(.tap)
559561
await store.receive(\.delegate.success, 43)
560562
}
563+
564+
func testSendCaseKeyPath() async {
565+
let store = TestStore<Int, Action>(initialState: 0) {
566+
Reduce { state, action in
567+
switch action {
568+
case .tap:
569+
return .send(.delegate(.success(42)))
570+
case .delegate:
571+
return .none
572+
case .view(.tap):
573+
state = state + 1
574+
return .send(.delegate(.success(42 * 42)))
575+
case let .view(.delete(indexSet)):
576+
let sum = indexSet.reduce(0, +)
577+
if sum == 42 {
578+
state = state + 1
579+
}
580+
return .send(.delegate(.success(sum)))
581+
}
582+
}
583+
}
584+
await store.send(\.tap)
585+
await store.receive(\.delegate.success, 42)
586+
587+
await store.send(\.view.tap) {
588+
$0 = 1
589+
}
590+
await store.receive(\.delegate.success, 42 * 42)
591+
592+
await store.send(\.view.delete, [0])
593+
await store.receive(\.delegate.success, 0)
594+
595+
await store.send(\.view.delete, [19, 23]) {
596+
$0 = 2
597+
}
598+
await store.receive(\.delegate.success, 42)
599+
}
561600
}
562601

563602
private struct Client: DependencyKey {
@@ -575,9 +614,15 @@
575614
private enum Action {
576615
case tap
577616
case delegate(Delegate)
617+
case view(View)
578618
@CasePathable
579619
enum Delegate {
580620
case success(Int)
581621
}
622+
@CasePathable
623+
enum View {
624+
case tap
625+
case delete(IndexSet)
626+
}
582627
}
583-
#endif
628+
#endif

0 commit comments

Comments
 (0)