Skip to content

Commit 2ea76a2

Browse files
authored
Add a few missing tests (#1539)
* Add some missing tests. * another test * fix 13.4 * wip * make some assertions more resilient * wip
1 parent 17ec3d9 commit 2ea76a2

File tree

8 files changed

+441
-86
lines changed

8 files changed

+441
-86
lines changed

Tests/ComposableArchitectureTests/EffectFailureTests.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
var line: UInt!
1414
XCTExpectFailure {
1515
$0.compactDescription == """
16-
An "EffectTask.task" returned from \
17-
"ComposableArchitectureTests/EffectFailureTests.swift:\(line+1)" threw an unhandled \
18-
error. …
16+
An "EffectTask.task" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
1917
2018
EffectFailureTests.Unexpected()
2119
@@ -39,9 +37,7 @@
3937
var line: UInt!
4038
XCTExpectFailure {
4139
$0.compactDescription == """
42-
An "EffectTask.run" returned from \
43-
"ComposableArchitectureTests/EffectFailureTests.swift:\(line+1)" threw an unhandled \
44-
error. …
40+
An "EffectTask.run" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
4541
4642
EffectFailureTests.Unexpected()
4743

Tests/ComposableArchitectureTests/EffectRunTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ final class EffectRunTests: XCTestCase {
4646
var line: UInt!
4747
XCTExpectFailure(nil, enabled: nil, strict: nil) {
4848
$0.compactDescription == """
49-
An "EffectTask.run" returned from \
50-
"ComposableArchitectureTests/EffectRunTests.swift:\(line+1)" threw an unhandled error. …
49+
An "EffectTask.run" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
5150
5251
EffectRunTests.Failure()
5352

Tests/ComposableArchitectureTests/EffectTaskTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ final class EffectTaskTests: XCTestCase {
4646
var line: UInt!
4747
XCTExpectFailure(nil, enabled: nil, strict: nil) {
4848
$0.compactDescription == """
49-
An "EffectTask.task" returned from \
50-
"ComposableArchitectureTests/EffectTaskTests.swift:\(line+1)" threw an unhandled error. …
49+
An "EffectTask.task" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
5150
5251
EffectTaskTests.Failure()
5352
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import ComposableArchitecture
2+
import XCTest
3+
4+
@MainActor
5+
final class ForEachReducerTests: XCTestCase {
6+
func testElementAction() async {
7+
let store = TestStore(
8+
initialState: Elements.State(
9+
rows: [
10+
.init(id: 1, value: "Blob"),
11+
.init(id: 2, value: "Blob Jr."),
12+
.init(id: 3, value: "Blob Sr."),
13+
]
14+
),
15+
reducer: Elements()
16+
)
17+
18+
await store.send(.row(id: 1, action: "Blob Esq.")) {
19+
$0.rows[id: 1]?.value = "Blob Esq."
20+
}
21+
await store.send(.row(id: 2, action: "")) {
22+
$0.rows[id: 2]?.value = ""
23+
}
24+
await store.receive(.row(id: 2, action: "Empty")) {
25+
$0.rows[id: 2]?.value = "Empty"
26+
}
27+
}
28+
29+
func testNonElementAction() async {
30+
let store = TestStore(
31+
initialState: Elements.State(),
32+
reducer: Elements()
33+
)
34+
35+
await store.send(.buttonTapped)
36+
}
37+
38+
#if DEBUG
39+
func testMissingElement() async {
40+
let store = TestStore(
41+
initialState: Elements.State(),
42+
reducer: EmptyReducer()
43+
.forEach(\.rows, action: /Elements.Action.row) {}
44+
)
45+
46+
XCTExpectFailure {
47+
$0.compactDescription == """
48+
A "forEach" at "\(#fileID):\(#line - 5)" received an action for a missing element.
49+
50+
Action:
51+
Elements.Action.row(id:, action:)
52+
53+
This is generally considered an application logic error, and can happen for a few reasons:
54+
55+
• A parent reducer removed an element with this ID before this reducer ran. This reducer \
56+
must run before any other reducer removes an element, which ensures that element reducers \
57+
can handle their actions while their state is still available.
58+
59+
• An in-flight effect emitted this action when state contained no element at this ID. \
60+
While it may be perfectly reasonable to ignore this action, consider canceling the \
61+
associated effect before an element is removed, especially if it is a long-living effect.
62+
63+
• This action was sent to the store while its state contained no element at this ID. To \
64+
fix this make sure that actions for this reducer can only be sent from a view store when \
65+
its state contains an element at this id. In SwiftUI applications, use "ForEachStore".
66+
"""
67+
}
68+
69+
await store.send(.row(id: 1, action: "Blob Esq."))
70+
}
71+
#endif
72+
}
73+
74+
struct Elements: ReducerProtocol {
75+
struct State: Equatable {
76+
struct Row: Equatable, Identifiable {
77+
var id: Int
78+
var value: String
79+
}
80+
var rows: IdentifiedArrayOf<Row> = []
81+
}
82+
enum Action: Equatable {
83+
case buttonTapped
84+
case row(id: Int, action: String)
85+
}
86+
var body: Reduce<State, Action> {
87+
Reduce<State, Action> { state, action in
88+
.none
89+
}
90+
.forEach(\.rows, action: /Action.row) {
91+
Reduce { state, action in
92+
state.value = action
93+
return action.isEmpty
94+
? .run { await $0("Empty") }
95+
: .none
96+
}
97+
}
98+
}
99+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import ComposableArchitecture
2+
import XCTest
3+
4+
@MainActor
5+
final class IfCaseLetReducerTests: XCTestCase {
6+
func testChildAction() async {
7+
struct SomeError: Error, Equatable {}
8+
9+
let store = TestStore(
10+
initialState: Result.success(0),
11+
reducer: Reduce<Result<Int, SomeError>, Result<Int, SomeError>> { state, action in
12+
.none
13+
}
14+
.ifCaseLet(/Result.success, action: /Result.success) {
15+
Reduce { state, action in
16+
state = action
17+
return state < 0 ? .run { await $0(0) } : .none
18+
}
19+
}
20+
)
21+
22+
await store.send(.success(1)) {
23+
$0 = .success(1)
24+
}
25+
await store.send(.failure(SomeError()))
26+
await store.send(.success(-1)) {
27+
$0 = .success(-1)
28+
}
29+
await store.receive(.success(0)) {
30+
$0 = .success(0)
31+
}
32+
}
33+
34+
#if DEBUG
35+
func testNilChild() async {
36+
struct SomeError: Error, Equatable {}
37+
38+
let store = TestStore(
39+
initialState: Result.failure(SomeError()),
40+
reducer: EmptyReducer<Result<Int, SomeError>, Result<Int, SomeError>>()
41+
.ifCaseLet(/Result.success, action: /Result.success) {}
42+
)
43+
44+
XCTExpectFailure {
45+
$0.compactDescription == """
46+
An "ifCaseLet" at "\(#fileID):\(#line - 5)" received a child action when child state was \
47+
set to a different case. …
48+
49+
Action:
50+
Result.success
51+
State:
52+
Result.failure
53+
54+
This is generally considered an application logic error, and can happen for a few reasons:
55+
56+
• A parent reducer set "Result" to a different case before this reducer ran. This reducer \
57+
must run before any other reducer sets child state to a different case. This ensures that \
58+
child reducers can handle their actions while their state is still available.
59+
60+
• An in-flight effect emitted this action when child state was unavailable. While it may \
61+
be perfectly reasonable to ignore this action, consider canceling the associated effect \
62+
before child state changes to another case, especially if it is a long-living effect.
63+
64+
• This action was sent to the store while state was another case. Make sure that actions \
65+
for this reducer can only be sent from a view store when state is set to the appropriate \
66+
case. In SwiftUI applications, use "SwitchStore".
67+
"""
68+
}
69+
70+
await store.send(.success(1))
71+
}
72+
#endif
73+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import ComposableArchitecture
2+
import XCTest
3+
4+
@MainActor
5+
final class IfLetReducerTests: XCTestCase {
6+
#if DEBUG
7+
func testNilChild() async {
8+
let store = TestStore(
9+
initialState: Int?.none,
10+
reducer: EmptyReducer<Int?, Void>()
11+
.ifLet(\.self, action: /.self) {}
12+
)
13+
14+
XCTExpectFailure {
15+
$0.compactDescription == """
16+
An "ifLet" at "\(#fileID):\(#line - 5)" received a child action when child state was \
17+
"nil". …
18+
19+
Action:
20+
()
21+
22+
This is generally considered an application logic error, and can happen for a few reasons:
23+
24+
• A parent reducer set child state to "nil" before this reducer ran. This reducer must run \
25+
before any other reducer sets child state to "nil". This ensures that child reducers can \
26+
handle their actions while their state is still available.
27+
28+
• An in-flight effect emitted this action when child state was "nil". While it may be \
29+
perfectly reasonable to ignore this action, consider canceling the associated effect \
30+
before child state becomes "nil", especially if it is a long-living effect.
31+
32+
• This action was sent to the store while state was "nil". Make sure that actions for this \
33+
reducer can only be sent from a view store when state is non-"nil". In SwiftUI \
34+
applications, use "IfLetStore".
35+
"""
36+
}
37+
38+
await store.send(())
39+
}
40+
#endif
41+
}

0 commit comments

Comments
 (0)