mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-14 20:35:56 +01:00
Update reportIssue formatting to avoid console errors (#3795)
When a non-ASCII character is fed to `reportIssue`, the following is output to the console: ``` <decode: bad range for [%@] got [offs:330 len:1073 within:0]> ``` This causes a lot of confusion for folks, so let's update the formatting to avoid this.
This commit is contained in:
@@ -146,7 +146,7 @@ final class RootCore<Root: Reducer>: Core {
|
||||
if isCompleted.value {
|
||||
reportIssue(
|
||||
"""
|
||||
An action was sent from a completed effect:
|
||||
An action was sent from a completed effect.
|
||||
|
||||
Action:
|
||||
\(debugCaseOutput(effectAction))
|
||||
|
||||
@@ -123,7 +123,7 @@ public struct DismissEffect: Sendable {
|
||||
else {
|
||||
reportIssue(
|
||||
"""
|
||||
A reducer requested dismissal at "\(fileID):\(line)", but couldn't be dismissed. …
|
||||
A reducer requested dismissal at "\(fileID):\(line)", but couldn't be dismissed.
|
||||
|
||||
This is generally considered an application logic error, and can happen when a reducer \
|
||||
assumes it runs in a presentation context. If a reducer can run at both the root level \
|
||||
|
||||
@@ -30,6 +30,6 @@ struct RecordMeetingTests {
|
||||
// ❌ The store received 1 unexpected action by the end of this test: …
|
||||
//
|
||||
// Unhandled actions:
|
||||
// • .timerTick
|
||||
// .timerTick
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ extension Effect {
|
||||
guard let handler else {
|
||||
reportIssue(
|
||||
"""
|
||||
An "Effect.run" returned from "\(fileID):\(line)" threw an unhandled error. …
|
||||
An "Effect.run" returned from "\(fileID):\(line)" threw an unhandled error.
|
||||
|
||||
\(String(customDumping: error).indent(by: 4))
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ extension TaskResult: Equatable where Success: Equatable {
|
||||
let lhsTypeName = typeName(lhsType)
|
||||
reportIssue(
|
||||
"""
|
||||
"\(lhsTypeName)" is not equatable. …
|
||||
"\(lhsTypeName)" is not equatable.
|
||||
|
||||
To test two values of this type, it must conform to the "Equatable" protocol. For \
|
||||
example:
|
||||
@@ -307,7 +307,7 @@ extension TaskResult: Hashable where Success: Hashable {
|
||||
let errorType = typeName(type(of: error))
|
||||
reportIssue(
|
||||
"""
|
||||
"\(errorType)" is not hashable. …
|
||||
"\(errorType)" is not hashable.
|
||||
|
||||
To hash a value of this type, it must conform to the "Hashable" protocol. For example:
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ extension BindingAction {
|
||||
}
|
||||
reportIssue(
|
||||
"""
|
||||
A binding action sent from a store was not handled. …
|
||||
A binding action sent from a store was not handled.
|
||||
|
||||
Action:
|
||||
\(typeName(Action.self)).binding(.set(_, \(valueDump)))
|
||||
|
||||
@@ -389,7 +389,7 @@ public struct _NavigationLinkStoreContent<State, Label: View>: View {
|
||||
"""
|
||||
reportIssue(
|
||||
"""
|
||||
A navigation link at "\(fileID):\(line)" is unpresentable. …
|
||||
A navigation link at "\(fileID):\(line)" is not presentable.
|
||||
|
||||
NavigationStack state element type:
|
||||
\(elementType)
|
||||
|
||||
@@ -500,16 +500,16 @@ extension Store where State: ObservableState {
|
||||
|
||||
func uncachedStoreWarning<State, Action>(_ store: Store<State, Action>) -> String {
|
||||
"""
|
||||
Scoping from uncached \(store) is not compatible with observation.
|
||||
Scoping from uncached '\(store)' is not compatible with observation.
|
||||
|
||||
This can happen for one of two reasons:
|
||||
|
||||
• A parent view scopes on a store using transform functions, which has been \
|
||||
1. A parent view scopes on a store using transform functions, which has been \
|
||||
deprecated, instead of with key paths and case paths. Read the migration guide for 1.5 \
|
||||
to update these scopes: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
|
||||
• A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
2. A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
'SwitchStore', 'ForEachStore', or any navigation view modifiers taking stores instead of \
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
|
||||
@@ -281,22 +281,22 @@ public struct _ForEachReducer<
|
||||
if state[keyPath: self.toElementsState][id: id] == nil {
|
||||
reportIssue(
|
||||
"""
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received an action for a missing element. …
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received an action for a missing element.
|
||||
|
||||
Action:
|
||||
\(debugCaseOutput(action))
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
must run before any other reducer removes an element, which ensures that element reducers \
|
||||
can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
While it may be perfectly reasonable to ignore this action, consider canceling the \
|
||||
associated effect before an element is removed, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while its state contained no element at this ID. To \
|
||||
This action was sent to the store while its state contained no element at this ID. To \
|
||||
fix this make sure that actions for this reducer can only be sent from a store when \
|
||||
its state contains an element at this id. In SwiftUI applications, use "ForEachStore".
|
||||
""",
|
||||
|
||||
@@ -215,7 +215,7 @@ public struct _IfCaseLetReducer<Parent: Reducer, Child: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
An "ifCaseLet" at "\(self.fileID):\(self.line)" received a child action when child state \
|
||||
was set to a different case. …
|
||||
was set to a different case.
|
||||
|
||||
Action:
|
||||
\(String(customDumping: action).indent(by: 4))
|
||||
@@ -224,16 +224,16 @@ public struct _IfCaseLetReducer<Parent: Reducer, Child: Reducer>: Reducer {
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set "\(typeName(Parent.State.self))" to a different case before this \
|
||||
A parent reducer set "\(typeName(Parent.State.self))" to a different case before this \
|
||||
reducer ran. This reducer must run before any other reducer sets child state to a \
|
||||
different case. This ensures that child reducers can handle their actions while their \
|
||||
state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
be perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state changes to another case, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was another case. Make sure that actions \
|
||||
This action was sent to the store while state was another case. Make sure that actions \
|
||||
for this reducer can only be sent from a store when state is set to the appropriate \
|
||||
case. In SwiftUI applications, use "SwitchStore".
|
||||
""",
|
||||
|
||||
@@ -287,22 +287,22 @@ public struct _IfLetReducer<Parent: Reducer, Child: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
An "ifLet" at "\(self.fileID):\(self.line)" received a child action when child state was \
|
||||
"nil". …
|
||||
"nil".
|
||||
|
||||
Action:
|
||||
\(String(customDumping: action).indent(by: 4))
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set child state to "nil" before this reducer ran. This reducer must run \
|
||||
A parent reducer set child state to "nil" before this reducer ran. This reducer must run \
|
||||
before any other reducer sets child state to "nil". This ensures that child reducers can \
|
||||
handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when child state was "nil". While it may be \
|
||||
An in-flight effect emitted this action when child state was "nil". While it may be \
|
||||
perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state becomes "nil", especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was "nil". Make sure that actions for this \
|
||||
This action was sent to the store while state was "nil". Make sure that actions for this \
|
||||
reducer can only be sent from a store when state is non-"nil". In SwiftUI \
|
||||
applications, use "IfLetStore".
|
||||
""",
|
||||
|
||||
@@ -632,7 +632,7 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
|
||||
reportIssue(
|
||||
"""
|
||||
An "ifLet" at "\(self.fileID):\(self.line)" received a presentation action when \
|
||||
destination state was absent. …
|
||||
destination state was absent.
|
||||
|
||||
Action:
|
||||
\(debugCaseOutput(action))
|
||||
@@ -640,11 +640,11 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
|
||||
This is generally considered an application logic error, and can happen for a few \
|
||||
reasons:
|
||||
|
||||
• A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
must run before any other reducer sets destination state to "nil". This ensures that \
|
||||
destination reducers can handle their actions while their state is still present.
|
||||
|
||||
• This action was sent to the store while destination state was "nil". Make sure that \
|
||||
This action was sent to the store while destination state was "nil". Make sure that \
|
||||
actions for this reducer can only be sent from a store when state is present, or \
|
||||
from effects that start from this reducer.
|
||||
""",
|
||||
|
||||
@@ -347,7 +347,7 @@ public struct Scope<ParentState, ParentAction, Child: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
A "Scope" at "\(fileID):\(line)" received a child action when child state was set to a \
|
||||
different case. …
|
||||
different case.
|
||||
|
||||
Action:
|
||||
\(debugCaseOutput(action))
|
||||
@@ -357,18 +357,18 @@ public struct Scope<ParentState, ParentAction, Child: Reducer>: Reducer {
|
||||
This is generally considered an application logic error, and can happen for a few \
|
||||
reasons:
|
||||
|
||||
• A parent reducer set "\(typeName(ParentState.self))" to a different case before the \
|
||||
A parent reducer set "\(typeName(ParentState.self))" to a different case before the \
|
||||
scoped reducer ran. Child reducers must run before any parent reducer sets child state \
|
||||
to a different case. This ensures that child reducers can handle their actions while \
|
||||
their state is still available. Consider using "Reducer.ifCaseLet" to embed this \
|
||||
child reducer in the parent reducer that change its state to ensure the child reducer \
|
||||
runs first.
|
||||
|
||||
• An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
be perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state changes to another case, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was another case. Make sure that actions \
|
||||
This action was sent to the store while state was another case. Make sure that actions \
|
||||
for this reducer can only be sent from a store when state is set to the appropriate \
|
||||
case. In SwiftUI applications, use "SwitchStore".
|
||||
""",
|
||||
|
||||
@@ -526,22 +526,22 @@ public struct _StackReducer<Base: Reducer, Destination: Reducer>: Reducer {
|
||||
} else {
|
||||
reportIssue(
|
||||
"""
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received an action for a missing element. …
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received an action for a missing element.
|
||||
|
||||
Action:
|
||||
\(debugCaseOutput(destinationAction))
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
must run before any other reducer removes an element, which ensures that element \
|
||||
reducers can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
While it may be perfectly reasonable to ignore this action, consider canceling the \
|
||||
associated effect before an element is removed, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while its state contained no element at this ID. To \
|
||||
This action was sent to the store while its state contained no element at this ID. To \
|
||||
fix this make sure that actions for this reducer can only be sent from a store when \
|
||||
its state contains an element at this id. In SwiftUI applications, use \
|
||||
"NavigationStack.init(path:)" with a binding to a store.
|
||||
@@ -566,7 +566,7 @@ public struct _StackReducer<Base: Reducer, Destination: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received a "popFrom" action for a missing \
|
||||
element. …
|
||||
element.
|
||||
|
||||
ID:
|
||||
\(id)
|
||||
@@ -586,7 +586,7 @@ public struct _StackReducer<Base: Reducer, Destination: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received a "push" action for an element it \
|
||||
already contains. …
|
||||
already contains.
|
||||
|
||||
ID:
|
||||
\(id)
|
||||
@@ -606,7 +606,7 @@ public struct _StackReducer<Base: Reducer, Destination: Reducer>: Reducer {
|
||||
reportIssue(
|
||||
"""
|
||||
A "forEach" at "\(self.fileID):\(self.line)" received a "push" action with an \
|
||||
unexpected generational ID. …
|
||||
unexpected generational ID.
|
||||
|
||||
Received ID:
|
||||
\(id)
|
||||
|
||||
@@ -788,7 +788,7 @@ extension WithViewStore where ViewState: Equatable, Content: View {
|
||||
"""
|
||||
A binding action sent from a store \
|
||||
\(context == .bindingState ? "for binding state defined " : "")at \
|
||||
"\(fileID):\(line)" was not handled. …
|
||||
"\(fileID):\(line)" was not handled.
|
||||
|
||||
Action:
|
||||
\(typeName(bindableActionType)).binding(.set(_, \(valueDump)))
|
||||
|
||||
@@ -663,26 +663,26 @@ public final class TestStore<State: Equatable, Action> {
|
||||
reportIssueHelper(
|
||||
"""
|
||||
An effect returned for this action is still running. It must complete before the end of \
|
||||
the test. …
|
||||
the test.
|
||||
|
||||
To fix, inspect any effects the reducer returns for this action and ensure that all of \
|
||||
them complete by the end of the test. There are a few reasons why an effect may not have \
|
||||
completed:
|
||||
|
||||
• If using async/await in your effect, it may need a little bit of time to properly \
|
||||
If using async/await in your effect, it may need a little bit of time to properly \
|
||||
finish. To fix you can simply perform "await store.finish()" at the end of your test.
|
||||
|
||||
• If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", etc.), \
|
||||
If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", etc.), \
|
||||
make sure that you wait enough time for it to perform the effect. If you are using a test \
|
||||
clock/scheduler, advance it so that the effects may complete, or consider using an \
|
||||
immediate clock/scheduler to immediately perform the effect instead.
|
||||
|
||||
• If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
then make sure those effects are torn down by marking the effect ".cancellable" and \
|
||||
returning a corresponding cancellation effect ("Effect.cancel") from another action, or, \
|
||||
if your effect is driven by a Combine subject, send it a completion.
|
||||
|
||||
• If you do not wish to assert on these effects, perform "await \
|
||||
If you do not wish to assert on these effects, perform "await \
|
||||
store.skipInFlightEffects()", or consider using a non-exhaustive test store: \
|
||||
"store.exhaustivity = .off".
|
||||
""",
|
||||
@@ -709,12 +709,12 @@ public final class TestStore<State: Equatable, Action> {
|
||||
if !self.reducer.receivedActions.isEmpty {
|
||||
let actions = self.reducer.receivedActions
|
||||
.map(\.action)
|
||||
.map { " • " + debugCaseOutput($0, abbreviated: true) }
|
||||
.map { " " + debugCaseOutput($0, abbreviated: true) }
|
||||
.joined(separator: "\n")
|
||||
reportIssueHelper(
|
||||
"""
|
||||
The store received \(self.reducer.receivedActions.count) unexpected \
|
||||
action\(self.reducer.receivedActions.count == 1 ? "" : "s"): …
|
||||
action\(self.reducer.receivedActions.count == 1 ? "" : "s").
|
||||
|
||||
Unhandled actions:
|
||||
\(actions)
|
||||
@@ -965,7 +965,7 @@ extension TestStore {
|
||||
reportIssueHelper(
|
||||
"""
|
||||
Must handle \(self.reducer.receivedActions.count) received \
|
||||
action\(self.reducer.receivedActions.count == 1 ? "" : "s") before sending an action: …
|
||||
action\(self.reducer.receivedActions.count == 1 ? "" : "s") before sending an action.
|
||||
|
||||
Unhandled actions: \(actions)
|
||||
""",
|
||||
@@ -1197,7 +1197,7 @@ extension TestStore {
|
||||
} catch {
|
||||
reportIssue(
|
||||
"""
|
||||
Skipped assertions: …
|
||||
Skipped assertions.
|
||||
|
||||
Threw error: \(error)
|
||||
""",
|
||||
@@ -1241,7 +1241,7 @@ extension TestStore {
|
||||
: "State was not expected to change, but a change occurred"
|
||||
reportIssueHelper(
|
||||
"""
|
||||
\(messageHeading): …
|
||||
\(messageHeading).
|
||||
|
||||
\(difference)\(postamble.isEmpty ? "" : "\n\n\(postamble)")
|
||||
""",
|
||||
@@ -1292,7 +1292,7 @@ extension TestStore where Action: Equatable {
|
||||
self.receiveAction(
|
||||
matching: { expectedAction == $0 },
|
||||
failureMessage: """
|
||||
Expected to receive the following action, but didn't: …
|
||||
Expected to receive the following action, but didn't:
|
||||
|
||||
\(expectedActionDump)
|
||||
""",
|
||||
@@ -2133,7 +2133,7 @@ extension TestStore {
|
||||
reportIssueHelper(
|
||||
"""
|
||||
\(actions.count) received action\
|
||||
\(actions.count == 1 ? " was" : "s were") skipped:
|
||||
\(actions.count == 1 ? " was" : "s were") skipped.
|
||||
|
||||
\(actionsDump)
|
||||
""",
|
||||
@@ -2151,7 +2151,7 @@ extension TestStore {
|
||||
.contains(where: { action, _ in predicate(receivedAction) })
|
||||
reportIssueHelper(
|
||||
"""
|
||||
Received unexpected action\(receivedActionLater ? " before this one" : ""): …
|
||||
Received unexpected action\(receivedActionLater ? " before this one" : ""):
|
||||
|
||||
\(unexpectedActionDescription(receivedAction))
|
||||
""",
|
||||
@@ -2427,7 +2427,7 @@ extension TestStore {
|
||||
reportIssueHelper(
|
||||
"""
|
||||
\(self.reducer.receivedActions.count) received action\
|
||||
\(self.reducer.receivedActions.count == 1 ? " was" : "s were") skipped:
|
||||
\(self.reducer.receivedActions.count == 1 ? " was" : "s were") skipped.
|
||||
|
||||
\(actions)
|
||||
""",
|
||||
@@ -2550,7 +2550,7 @@ extension TestStore {
|
||||
withExpectedIssue {
|
||||
reportIssue(
|
||||
"""
|
||||
Skipped assertions: …
|
||||
Skipped assertions.
|
||||
|
||||
\(message)
|
||||
""",
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
|
||||
var line: UInt!
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An "Effect.run" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "Effect.run" returned from "\(#fileID):\(line+1)" threw an unhandled error.
|
||||
|
||||
EffectFailureTests.Unexpected()
|
||||
|
||||
All non-cancellation errors must be explicitly handled via the "catch" parameter on \
|
||||
"Effect.run", or via a "do" block.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
line = #line
|
||||
|
||||
@@ -47,14 +47,16 @@ final class EffectRunTests: BaseTCATestCase {
|
||||
func testRunUnhandledFailure() async {
|
||||
var line: UInt!
|
||||
XCTExpectFailure(nil, enabled: nil, strict: nil) {
|
||||
$0.compactDescription == """
|
||||
failed - An "Effect.run" returned from "\(#fileID):\(line+1)" threw an unhandled error. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "Effect.run" returned from "\(#fileID):\(line+1)" threw an unhandled error.
|
||||
|
||||
EffectRunTests.Failure()
|
||||
|
||||
All non-cancellation errors must be explicitly handled via the "catch" parameter on \
|
||||
"Effect.run", or via a "do" block.
|
||||
"""
|
||||
)
|
||||
}
|
||||
struct State: Equatable {}
|
||||
enum Action: Equatable { case tapped, response }
|
||||
@@ -126,8 +128,9 @@ final class EffectRunTests: BaseTCATestCase {
|
||||
@MainActor
|
||||
func testRunEscapeFailure() async throws {
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An action was sent from a completed effect:
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An action was sent from a completed effect.
|
||||
|
||||
Action:
|
||||
EffectRunTests.Action.response
|
||||
@@ -141,6 +144,7 @@ final class EffectRunTests: BaseTCATestCase {
|
||||
To fix this, make sure that your 'run' closure does not return until you're done \
|
||||
calling 'send'.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
enum Action { case tap, response }
|
||||
|
||||
@@ -78,11 +78,7 @@ final class ObservableTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
func testReplace() async {
|
||||
#if swift(<6.2)
|
||||
if #available(iOS 17, macOS 14, tvOS 14, watchOS 10, *) {
|
||||
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
||||
}
|
||||
#endif
|
||||
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
||||
var state = ChildState(count: 42)
|
||||
let didChange = LockIsolated(false)
|
||||
|
||||
@@ -98,12 +94,7 @@ final class ObservableTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
func testReset() async {
|
||||
#if swift(<6.2)
|
||||
if #available(iOS 17, macOS 14, tvOS 14, watchOS 10, *) {
|
||||
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
||||
}
|
||||
#endif
|
||||
|
||||
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
||||
var state = ChildState(count: 42)
|
||||
let didChange = LockIsolated(false)
|
||||
|
||||
|
||||
@@ -41,27 +41,28 @@ final class ForEachReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "forEach" at "\(#fileID):\(#line - 5)" received an action for a missing \
|
||||
element. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "forEach" at "\(#fileID):\(#line - 6)" received an action for a missing element.
|
||||
|
||||
Action:
|
||||
Elements.Action.rows(.element(id:, action:))
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
must run before any other reducer removes an element, which ensures that element \
|
||||
reducers can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
While it may be perfectly reasonable to ignore this action, consider canceling the \
|
||||
associated effect before an element is removed, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while its state contained no element at this ID. To \
|
||||
This action was sent to the store while its state contained no element at this ID. To \
|
||||
fix this make sure that actions for this reducer can only be sent from a store when \
|
||||
its state contains an element at this id. In SwiftUI applications, use "ForEachStore".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(\.rows[id: 1], "Blob Esq.")
|
||||
|
||||
@@ -39,9 +39,10 @@ final class IfCaseLetReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An "ifCaseLet" at "\(#fileID):\(#line - 5)" received a child action when child \
|
||||
state was set to a different case. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "ifCaseLet" at "\(#fileID):\(#line - 6)" received a child action when child state was \
|
||||
set to a different case.
|
||||
|
||||
Action:
|
||||
Result.success(1)
|
||||
@@ -50,18 +51,19 @@ final class IfCaseLetReducerTests: BaseTCATestCase {
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set "Result" to a different case before this reducer ran. This \
|
||||
A parent reducer set "Result" to a different case before this reducer ran. This \
|
||||
reducer must run before any other reducer sets child state to a different case. This \
|
||||
ensures that child reducers can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
be perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state changes to another case, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was another case. Make sure that actions \
|
||||
This action was sent to the store while state was another case. Make sure that actions \
|
||||
for this reducer can only be sent from a store when state is set to the appropriate \
|
||||
case. In SwiftUI applications, use "SwitchStore".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.success(1))
|
||||
|
||||
@@ -10,9 +10,10 @@ final class IfLetReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An "ifLet" at "\(#fileID):\(#line - 5)" received a child action when child state \
|
||||
was "nil". …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "ifLet" at "\(#fileID):\(#line - 6)" received a child action when child state \
|
||||
was "nil".
|
||||
|
||||
Action:
|
||||
()
|
||||
@@ -20,18 +21,19 @@ final class IfLetReducerTests: BaseTCATestCase {
|
||||
This is generally considered an application logic error, and can happen for a few \
|
||||
reasons:
|
||||
|
||||
• A parent reducer set child state to "nil" before this reducer ran. This reducer must \
|
||||
A parent reducer set child state to "nil" before this reducer ran. This reducer must \
|
||||
run before any other reducer sets child state to "nil". This ensures that child \
|
||||
reducers can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when child state was "nil". While it may be \
|
||||
An in-flight effect emitted this action when child state was "nil". While it may be \
|
||||
perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state becomes "nil", especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was "nil". Make sure that actions for \
|
||||
This action was sent to the store while state was "nil". Make sure that actions for \
|
||||
this reducer can only be sent from a store when state is non-"nil". In SwiftUI \
|
||||
applications, use "IfLetStore".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(())
|
||||
|
||||
@@ -37,17 +37,21 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
parent.$child[case: /Child.text]?.append("!")
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Can't modify unrelated case "int"
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Can't modify unrelated case "int"
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
parent.$child[case: /Child.text] = nil
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Can't modify unrelated case "int"
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Can't modify unrelated case "int"
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
XCTAssertEqual(parent.child, .int(42))
|
||||
@@ -937,7 +941,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
}
|
||||
)
|
||||
return .none
|
||||
case let .presentChild(id):
|
||||
case .presentChild(let id):
|
||||
state.destination = .child(Child.State(id: id ?? self.uuid()))
|
||||
return .none
|
||||
}
|
||||
@@ -1219,7 +1223,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .startButtonTapped:
|
||||
@@ -1315,7 +1319,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .startButtonTapped:
|
||||
@@ -1418,7 +1422,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .startButtonTapped:
|
||||
@@ -1545,7 +1549,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
case .presentChild:
|
||||
state.child = Child.State()
|
||||
return .none
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .startButtonTapped:
|
||||
@@ -1722,24 +1726,26 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An "ifLet" at \
|
||||
"ComposableArchitectureTests/PresentationReducerTests.swift:\(#line - 13)" received a \
|
||||
presentation action when destination state was absent. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "ifLet" at \
|
||||
"ComposableArchitectureTests/PresentationReducerTests.swift:\(#line - 14)" received a \
|
||||
presentation action when destination state was absent.
|
||||
|
||||
Action:
|
||||
PresentationReducerTests.Parent.Action.child(.dismiss)
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
must run before any other reducer sets destination state to "nil". This ensures that \
|
||||
destination reducers can handle their actions while their state is still present.
|
||||
|
||||
• This action was sent to the store while destination state was "nil". Make sure that \
|
||||
This action was sent to the store while destination state was "nil". Make sure that \
|
||||
actions for this reducer can only be sent from a store when state is present, or \
|
||||
from effects that start from this reducer.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.child(.dismiss))
|
||||
@@ -1778,24 +1784,26 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An "ifLet" at \
|
||||
"ComposableArchitectureTests/PresentationReducerTests.swift:\(#line - 13)" received a \
|
||||
presentation action when destination state was absent. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An "ifLet" at \
|
||||
"ComposableArchitectureTests/PresentationReducerTests.swift:\(#line - 14)" received a \
|
||||
presentation action when destination state was absent.
|
||||
|
||||
Action:
|
||||
PresentationReducerTests.Parent.Action.child(.presented(.tap))
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
A parent reducer set destination state to "nil" before this reducer ran. This reducer \
|
||||
must run before any other reducer sets destination state to "nil". This ensures that \
|
||||
destination reducers can handle their actions while their state is still present.
|
||||
|
||||
• This action was sent to the store while destination state was "nil". Make sure that \
|
||||
This action was sent to the store while destination state was "nil". Make sure that \
|
||||
actions for this reducer can only be sent from a store when state is present, or \
|
||||
from effects that start from this reducer.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.child(.presented(.tap)))
|
||||
@@ -2104,7 +2112,8 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
ConfirmationDialogState {
|
||||
TextState("Hello!")
|
||||
} actions: {
|
||||
})
|
||||
}
|
||||
)
|
||||
return .none
|
||||
case .destination(.presented(.dialog(.showAlert))):
|
||||
state.destination = .alert(AlertState { TextState("Hello!") })
|
||||
@@ -2150,7 +2159,8 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
ConfirmationDialogState {
|
||||
TextState("Hello!")
|
||||
} actions: {
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
await store.send(.destination(.dismiss)) {
|
||||
$0.destination = nil
|
||||
@@ -2258,31 +2268,33 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
$0.sourceCodeContext.location?.fileURL.absoluteString.contains("BaseTCATestCase") == true
|
||||
|| $0.sourceCodeContext.location?.lineNumber == line + 1
|
||||
&& $0.compactDescription == """
|
||||
failed - An effect returned for this action is still running. It must complete before \
|
||||
the end of the test. …
|
||||
&& $0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An effect returned for this action is still running. It must complete before the end \
|
||||
of the test.
|
||||
|
||||
To fix, inspect any effects the reducer returns for this action and ensure that all of \
|
||||
them complete by the end of the test. There are a few reasons why an effect may not \
|
||||
have completed:
|
||||
|
||||
• If using async/await in your effect, it may need a little bit of time to properly \
|
||||
If using async/await in your effect, it may need a little bit of time to properly \
|
||||
finish. To fix you can simply perform "await store.finish()" at the end of your test.
|
||||
|
||||
• If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", \
|
||||
If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", \
|
||||
etc.), make sure that you wait enough time for it to perform the effect. If you are \
|
||||
using a test clock/scheduler, advance it so that the effects may complete, or consider \
|
||||
using an immediate clock/scheduler to immediately perform the effect instead.
|
||||
|
||||
• If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
then make sure those effects are torn down by marking the effect ".cancellable" and \
|
||||
returning a corresponding cancellation effect ("Effect.cancel") from another action, \
|
||||
or, if your effect is driven by a Combine subject, send it a completion.
|
||||
|
||||
• If you do not wish to assert on these effects, perform "await \
|
||||
If you do not wish to assert on these effects, perform "await \
|
||||
store.skipInFlightEffects()", or consider using a non-exhaustive test store: \
|
||||
"store.exhaustivity = .off".
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2300,7 +2312,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .tap:
|
||||
@@ -2337,7 +2349,7 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
await send(.response(42))
|
||||
}
|
||||
.cancellable(id: Child.CancelID())
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
}
|
||||
@@ -2571,10 +2583,10 @@ final class PresentationReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription.hasPrefix(
|
||||
$0.compactDescription.contains(
|
||||
"""
|
||||
failed - A "Scope" at "\(#fileID):\(line)" received a child action when child state was \
|
||||
set to a different case. …
|
||||
A "Scope" at "\(#fileID):\(line)" received a child action when child state was \
|
||||
set to a different case.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,17 +28,21 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
stack[id: 0, case: /Element.text]?.append("!")
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Can't modify unrelated case "int"
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Can't modify unrelated case "int"
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
stack[id: 0, case: /Element.text] = nil
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Can't modify unrelated case "int"
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Can't modify unrelated case "int"
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
XCTAssertEqual(Array(stack), [.int(42)])
|
||||
@@ -261,8 +265,9 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Received unexpected action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Received unexpected action:
|
||||
|
||||
StackReducerTests.Parent.Action.children(
|
||||
− .popFrom(id: #1)
|
||||
@@ -271,6 +276,7 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
|
||||
(Expected: −, Received: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.children(.element(id: 0, action: .tap)))
|
||||
@@ -561,7 +567,7 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
switch action {
|
||||
case .cancel:
|
||||
return .cancel(id: CancelID.cancel)
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
case .tap:
|
||||
@@ -661,7 +667,7 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count += value
|
||||
return .none
|
||||
case .tap:
|
||||
@@ -774,28 +780,30 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
let line = #line - 3
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received an action for a missing element. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" received an \
|
||||
action for a missing element.
|
||||
|
||||
Action:
|
||||
()
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
A parent reducer removed an element with this ID before this reducer ran. This reducer \
|
||||
must run before any other reducer removes an element, which ensures that element \
|
||||
reducers can handle their actions while their state is still available.
|
||||
|
||||
• An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
An in-flight effect emitted this action when state contained no element at this ID. \
|
||||
While it may be perfectly reasonable to ignore this action, consider canceling the \
|
||||
associated effect before an element is removed, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while its state contained no element at this ID. To \
|
||||
This action was sent to the store while its state contained no element at this ID. To \
|
||||
fix this make sure that actions for this reducer can only be sent from a store when \
|
||||
its state contains an element at this id. In SwiftUI applications, use \
|
||||
"NavigationStack.init(path:)" with a binding to a store.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
var path = StackState<Int>()
|
||||
@@ -823,15 +831,17 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
let line = #line - 3
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received a "popFrom" action for a missing element. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received a "popFrom" action for a missing element.
|
||||
|
||||
ID:
|
||||
#999
|
||||
Path IDs:
|
||||
[#0]
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
let store = TestStore(initialState: Parent.State(path: StackState<Int>([1]))) {
|
||||
@@ -874,31 +884,33 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
$0.sourceCodeContext.location?.fileURL.absoluteString.contains("BaseTCATestCase") == true
|
||||
|| $0.sourceCodeContext.location?.lineNumber == line + 1
|
||||
&& $0.compactDescription == """
|
||||
failed - An effect returned for this action is still running. It must complete before \
|
||||
the end of the test. …
|
||||
&& $0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An effect returned for this action is still running. It must complete before \
|
||||
the end of the test.
|
||||
|
||||
To fix, inspect any effects the reducer returns for this action and ensure that all \
|
||||
of them complete by the end of the test. There are a few reasons why an effect may \
|
||||
not have completed:
|
||||
|
||||
• If using async/await in your effect, it may need a little bit of time to properly \
|
||||
If using async/await in your effect, it may need a little bit of time to properly \
|
||||
finish. To fix you can simply perform "await store.finish()" at the end of your test.
|
||||
|
||||
• If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", \
|
||||
If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", \
|
||||
etc.), make sure that you wait enough time for it to perform the effect. If you are \
|
||||
using a test clock/scheduler, advance it so that the effects may complete, or \
|
||||
consider using an immediate clock/scheduler to immediately perform the effect instead.
|
||||
|
||||
• If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
If you are returning a long-living effect (timers, notifications, subjects, etc.), \
|
||||
then make sure those effects are torn down by marking the effect ".cancellable" and \
|
||||
returning a corresponding cancellation effect ("Effect.cancel") from another action, \
|
||||
or, if your effect is driven by a Combine subject, send it a completion.
|
||||
|
||||
• If you do not wish to assert on these effects, perform "await \
|
||||
If you do not wish to assert on these effects, perform "await \
|
||||
store.skipInFlightEffects()", or consider using a non-exhaustive test store: \
|
||||
"store.exhaustivity = .off".
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,7 +930,7 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
try await self.mainQueue.sleep(for: .seconds(count))
|
||||
await send(.response(42))
|
||||
}
|
||||
case let .response(value):
|
||||
case .response(let value):
|
||||
state.count = value
|
||||
return .none
|
||||
}
|
||||
@@ -1084,15 +1096,17 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received a "push" action for an element it already contains. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received a "push" action for an element it already contains.
|
||||
|
||||
ID:
|
||||
#0
|
||||
Path IDs:
|
||||
[#0]
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.child(.push(id: 0, state: Child.State()))) {
|
||||
@@ -1128,15 +1142,17 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" \
|
||||
received a "push" action with an unexpected generational ID. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "forEach" at "ComposableArchitectureTests/StackReducerTests.swift:\(line)" received a \
|
||||
"push" action with an unexpected generational ID.
|
||||
|
||||
Received ID:
|
||||
#1
|
||||
Expected ID:
|
||||
#0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.child(.push(id: 1, state: Child.State()))) {
|
||||
@@ -1169,8 +1185,9 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A state change does not match expectation: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A state change does not match expectation.
|
||||
|
||||
StackReducerTests.Parent.State(
|
||||
children: [
|
||||
@@ -1181,6 +1198,7 @@ final class StackReducerTests: BaseTCATestCase {
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.child(.push(id: 0, state: Child.State()))) {
|
||||
$0.children[id: 1] = Child.State()
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
let store = Store<State, Action>(initialState: State()) {}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A binding action sent from a store for binding state defined at \
|
||||
"\(#fileID):\(line)" was not handled. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A binding action sent from a store for binding state defined at "\(#fileID):\(line)" was \
|
||||
not handled.
|
||||
|
||||
Action:
|
||||
RuntimeWarningTests.Action.binding(.set(_, 42))
|
||||
|
||||
To fix this, invoke "BindingReducer()" from your feature reducer's "body".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
let viewStore = ViewStore(store, observe: { $0 })
|
||||
@@ -46,14 +48,16 @@
|
||||
let store = Store<State, Action>(initialState: State()) {}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A binding action sent from a store was not handled. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A binding action sent from a store was not handled.
|
||||
|
||||
Action:
|
||||
RuntimeWarningTests.Action.binding(.set(_, 42))
|
||||
|
||||
To fix this, invoke "BindingReducer()" from your feature reducer's "body".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
store.count = 42
|
||||
@@ -72,15 +76,17 @@
|
||||
let store = Store<State, Action>(initialState: State()) {}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A binding action sent from a store for binding state defined at \
|
||||
"\(#fileID):\(line)" was not handled. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A binding action sent from a store for binding state defined at "\(#fileID):\(line)" was \
|
||||
not handled.
|
||||
|
||||
Action:
|
||||
RuntimeWarningTests.Action.binding(.set(_, 42))
|
||||
|
||||
To fix this, invoke "BindingReducer()" from your feature reducer's "body".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
let viewStore = ViewStore(store, observe: { $0 })
|
||||
@@ -112,11 +118,12 @@
|
||||
column: 1
|
||||
] = .init()
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - A navigation stack binding at "file.swift:1" was written to with a path that \
|
||||
has the same number of elements that already exist in the store. A view should only \
|
||||
write to this binding with a path that has pushed a new element onto the stack, or \
|
||||
popped one or more elements from the stack.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A navigation stack binding at "file.swift:1" was written to with a path that has the \
|
||||
same number of elements that already exist in the store. A view should only write to \
|
||||
this binding with a path that has pushed a new element onto the stack, or popped one or \
|
||||
more elements from the stack.
|
||||
|
||||
This usually means the "forEach" has not been integrated with the reducer powering the \
|
||||
store, and this reducer is responsible for handling stack actions.
|
||||
@@ -133,6 +140,7 @@
|
||||
And ensure that every parent reducer is integrated into the root reducer that powers \
|
||||
the store.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,9 +176,9 @@
|
||||
column: 1
|
||||
] = nil
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - A binding at "file.swift:1" was set to "nil", but the store destination wasn't \
|
||||
nil'd out.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A binding at "file.swift:1" was set to "nil", but the store destination wasn't nil'd out.
|
||||
|
||||
This usually means an "ifLet" has not been integrated with the reducer powering the \
|
||||
store, and this reducer is responsible for handling presentation actions.
|
||||
@@ -187,6 +195,7 @@
|
||||
And ensure that every parent reducer is integrated into the root reducer that powers the \
|
||||
store.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@ final class ScopeCacheTests: BaseTCATestCase {
|
||||
.scope(state: \.child, action: \.child.presented)?
|
||||
.send(.show)
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Scoping from uncached StoreOf<Feature> is not compatible with observation.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Scoping from uncached 'StoreOf<Feature>' is not compatible with observation.
|
||||
|
||||
This can happen for one of two reasons:
|
||||
|
||||
• A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
1. A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
instead of with key paths and case paths. Read the migration guide for 1.5 to update these \
|
||||
scopes: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
scopes: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
|
||||
• A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
2. A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
'SwitchStore', 'ForEachStore', or any navigation view modifiers taking stores instead of \
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
"""
|
||||
)
|
||||
}
|
||||
store.send(.child(.dismiss))
|
||||
}
|
||||
@@ -76,21 +76,21 @@ final class ScopeCacheTests: BaseTCATestCase {
|
||||
}
|
||||
_ = cancellable
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Scoping from uncached StoreOf<Feature> is not compatible with observation.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Scoping from uncached 'StoreOf<Feature>' is not compatible with observation.
|
||||
|
||||
This can happen for one of two reasons:
|
||||
|
||||
• A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
1. A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
instead of with key paths and case paths. Read the migration guide for 1.5 to update these \
|
||||
scopes: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
scopes: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
|
||||
• A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
2. A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
'SwitchStore', 'ForEachStore', or any navigation view modifiers taking stores instead of \
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,21 +122,21 @@ final class ScopeCacheTests: BaseTCATestCase {
|
||||
.scope(state: \.rows, action: \.rows)
|
||||
)
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Scoping from uncached StoreOf<Feature> is not compatible with observation.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Scoping from uncached 'StoreOf<Feature>' is not compatible with observation.
|
||||
|
||||
This can happen for one of two reasons:
|
||||
|
||||
• A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
1. A parent view scopes on a store using transform functions, which has been deprecated, \
|
||||
instead of with key paths and case paths. Read the migration guide for 1.5 to update these \
|
||||
scopes: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
scopes: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5
|
||||
|
||||
• A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
2. A parent feature is using deprecated navigation APIs, such as 'IfLetStore', \
|
||||
'SwitchStore', 'ForEachStore', or any navigation view modifiers taking stores instead of \
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: \
|
||||
https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
bindings. Read the migration guide for 1.7 to update those APIs: https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,10 @@ final class ScopeTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A "Scope" at "\(#fileID):\(#line - 5)" received a child action when child state \
|
||||
was set to a different case. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A "Scope" at "\(#fileID):\(#line - 6)" received a child action when child state \
|
||||
was set to a different case.
|
||||
|
||||
Action:
|
||||
Child2.Action.name
|
||||
@@ -56,20 +57,21 @@ final class ScopeTests: BaseTCATestCase {
|
||||
|
||||
This is generally considered an application logic error, and can happen for a few reasons:
|
||||
|
||||
• A parent reducer set "Child2.State" to a different case before the scoped reducer ran. \
|
||||
A parent reducer set "Child2.State" to a different case before the scoped reducer ran. \
|
||||
Child reducers must run before any parent reducer sets child state to a different case. \
|
||||
This ensures that child reducers can handle their actions while their state is still \
|
||||
available. Consider using "Reducer.ifCaseLet" to embed this child reducer in the \
|
||||
parent reducer that change its state to ensure the child reducer runs first.
|
||||
|
||||
• An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
An in-flight effect emitted this action when child state was unavailable. While it may \
|
||||
be perfectly reasonable to ignore this action, consider canceling the associated effect \
|
||||
before child state changes to another case, especially if it is a long-living effect.
|
||||
|
||||
• This action was sent to the store while state was another case. Make sure that actions \
|
||||
This action was sent to the store while state was another case. Make sure that actions \
|
||||
for this reducer can only be sent from a store when state is set to the appropriate \
|
||||
case. In SwiftUI applications, use "SwitchStore".
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.send(.name("Blob"))
|
||||
|
||||
@@ -14,8 +14,9 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
TaskResult<Never>.failure(Failure(message: "Something went wrong"))
|
||||
)
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - "TaskResultTests.Failure" is not equatable. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
"TaskResultTests.Failure" is not equatable.
|
||||
|
||||
To test two values of this type, it must conform to the "Equatable" protocol. For example:
|
||||
|
||||
@@ -23,6 +24,7 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
|
||||
See the documentation of "TaskResult" for more information.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +42,9 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
TaskResult<Never>.failure(Failure2(message: "Something went wrong"))
|
||||
)
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - Difference: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Difference: …
|
||||
|
||||
TaskResult.failure(
|
||||
− TaskResultTests.Failure1(message: "Something went wrong")
|
||||
@@ -50,6 +53,7 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
|
||||
(First: −, Second: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +65,9 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
_ = TaskResult<Never>.failure(Failure(message: "Something went wrong")).hashValue
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - "TaskResultTests.Failure" is not hashable. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
"TaskResultTests.Failure" is not hashable.
|
||||
|
||||
To hash a value of this type, it must conform to the "Hashable" protocol. For example:
|
||||
|
||||
@@ -70,6 +75,7 @@ final class TaskResultTests: BaseTCATestCase {
|
||||
|
||||
See the documentation of "TaskResult" for more information.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -15,22 +15,26 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Expected state to change, but no change occurred.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected state to change, but no change occurred.
|
||||
|
||||
The trailing closure made no observable modifications to state. If no change to state is \
|
||||
expected, omit the trailing closure.
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.first) { _ = $0 }
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Expected state to change, but no change occurred.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected state to change, but no change occurred.
|
||||
|
||||
The trailing closure made no observable modifications to state. If no change to state is \
|
||||
expected, omit the trailing closure.
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.receive(.second) { _ = $0 }
|
||||
}
|
||||
@@ -46,14 +50,16 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A state change does not match expectation: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A state change does not match expectation.
|
||||
|
||||
− TestStoreFailureTests.State(count: 0)
|
||||
+ TestStoreFailureTests.State(count: 1)
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(()) { $0.count = 0 }
|
||||
}
|
||||
@@ -69,14 +75,16 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - State was not expected to change, but a change occurred: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
State was not expected to change, but a change occurred.
|
||||
|
||||
− TestStoreFailureTests.State(count: 0)
|
||||
+ TestStoreFailureTests.State(count: 1)
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(())
|
||||
}
|
||||
@@ -98,14 +106,16 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
|
||||
await store.send(.first)
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - State was not expected to change, but a change occurred: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
State was not expected to change, but a change occurred.
|
||||
|
||||
− TestStoreFailureTests.State(count: 0)
|
||||
+ TestStoreFailureTests.State(count: 1)
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.receive(.second)
|
||||
}
|
||||
@@ -123,16 +133,18 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - The store received 1 unexpected action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
The store received 1 unexpected action.
|
||||
|
||||
Unhandled actions:
|
||||
• .second
|
||||
.second
|
||||
|
||||
To fix, explicitly assert against these actions using "store.receive", skip these actions \
|
||||
by performing "await store.skipReceivedActions()", or consider using a non-exhaustive test \
|
||||
store: "store.exhaustivity = .off".
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.first)
|
||||
}
|
||||
@@ -152,16 +164,18 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
|
||||
await store.send(.first)
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - The store received 1 unexpected action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
The store received 1 unexpected action.
|
||||
|
||||
Unhandled actions:
|
||||
• .second
|
||||
.second
|
||||
|
||||
To fix, explicitly assert against these actions using "store.receive", skip these actions \
|
||||
by performing "await store.skipReceivedActions()", or consider using a non-exhaustive test \
|
||||
store: "store.exhaustivity = .off".
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.finish()
|
||||
}
|
||||
@@ -176,31 +190,33 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - An effect returned for this action is still running. It must complete before the \
|
||||
end of the test. …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
An effect returned for this action is still running. It must complete before the end of \
|
||||
the test.
|
||||
|
||||
To fix, inspect any effects the reducer returns for this action and ensure that all of \
|
||||
them complete by the end of the test. There are a few reasons why an effect may not have \
|
||||
completed:
|
||||
|
||||
• If using async/await in your effect, it may need a little bit of time to properly \
|
||||
If using async/await in your effect, it may need a little bit of time to properly \
|
||||
finish. To fix you can simply perform "await store.finish()" at the end of your test.
|
||||
|
||||
• If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", etc.), \
|
||||
If an effect uses a clock (or scheduler, via "receive(on:)", "delay", "debounce", etc.), \
|
||||
make sure that you wait enough time for it to perform the effect. If you are using a test \
|
||||
clock/scheduler, advance it so that the effects may complete, or consider using an \
|
||||
immediate clock/scheduler to immediately perform the effect instead.
|
||||
|
||||
• If you are returning a long-living effect (timers, notifications, subjects, etc.), then \
|
||||
If you are returning a long-living effect (timers, notifications, subjects, etc.), then \
|
||||
make sure those effects are torn down by marking the effect ".cancellable" and returning a \
|
||||
corresponding cancellation effect ("Effect.cancel") from another action, or, if your \
|
||||
effect is driven by a Combine subject, send it a completion.
|
||||
|
||||
• If you do not wish to assert on these effects, perform "await \
|
||||
If you do not wish to assert on these effects, perform "await \
|
||||
store.skipInFlightEffects()", or consider using a non-exhaustive test store: \
|
||||
"store.exhaustivity = .off".
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(())
|
||||
}
|
||||
@@ -220,13 +236,15 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
await store.send(.first)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Must handle 1 received action before sending an action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Must handle 1 received action before sending an action.
|
||||
|
||||
Unhandled actions: [
|
||||
[0]: .second
|
||||
]
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.first)
|
||||
|
||||
@@ -242,11 +260,13 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Expected to receive the following action, but didn't: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected to receive the following action, but didn't:
|
||||
|
||||
TestStoreFailureTests.Action.action
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.receive(.action)
|
||||
}
|
||||
@@ -269,14 +289,16 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
await store.send(.first)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Received unexpected action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Received unexpected action:
|
||||
|
||||
− TestStoreFailureTests.Action.first
|
||||
+ TestStoreFailureTests.Action.second
|
||||
|
||||
(Expected: −, Received: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.receive(.first)
|
||||
}
|
||||
@@ -288,7 +310,7 @@ final class TestStoreFailureTests: BaseTCATestCase {
|
||||
}
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == "failed - Threw error: SomeError()"
|
||||
$0.compactDescription.hasSuffix("Threw error: SomeError()")
|
||||
}
|
||||
await store.send(()) { _ in
|
||||
struct SomeError: Error {}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
await store.receive(false) { $0 = 2 }
|
||||
XCTAssertEqual(store.state, 2)
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == "failed - There were no received actions to skip."
|
||||
$0.compactDescription.hasSuffix("There were no received actions to skip.")
|
||||
}
|
||||
await store.skipReceivedActions(strict: true)
|
||||
}
|
||||
@@ -111,7 +111,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
let task = await store.send(true)
|
||||
await task.finish(timeout: NSEC_PER_SEC / 2)
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == "failed - There were no in-flight effects to skip."
|
||||
$0.compactDescription.hasSuffix("There were no in-flight effects to skip.")
|
||||
}
|
||||
await store.skipInFlightEffects(strict: true)
|
||||
}
|
||||
@@ -239,8 +239,9 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
store.exhaustivity = .off(showSkippedAssertions: true)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A state change does not match expectation: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A state change does not match expectation.
|
||||
|
||||
Counter.State(
|
||||
− count: 0,
|
||||
@@ -250,6 +251,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.increment) {
|
||||
$0.count = 0
|
||||
@@ -341,8 +343,9 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
$0.count = 1
|
||||
}
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - A state change does not match expectation: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A state change does not match expectation.
|
||||
|
||||
TestStoreNonExhaustiveTests.State(
|
||||
− count: 2,
|
||||
@@ -352,6 +355,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.receive(.loggedInResponse(true)) {
|
||||
$0.count = 2
|
||||
@@ -629,9 +633,11 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
await store.send(.onAppear)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Expected to receive an action matching case path, but didn't get one.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected to receive an action matching case path, but didn't get one.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.receive(\.onAppear)
|
||||
@@ -650,9 +656,11 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
await store.receive(\.response2)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Expected to receive an action matching case path, but didn't get one.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected to receive an action matching case path, but didn't get one.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
await store.receive(\.response2)
|
||||
@@ -684,9 +692,11 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
XCTModify(&state.child) { _ in }
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - XCTModify: Expected "Int" value to be modified but it was unchanged.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected "Int" value to be modified but it was unchanged.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
await store.receive(.response) { state in
|
||||
@@ -694,9 +704,11 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase {
|
||||
XCTExpectFailure {
|
||||
XCTModify(&state.child) { _ in }
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - XCTModify: Expected "Int" value to be modified but it was unchanged.
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Expected "Int" value to be modified but it was unchanged.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,14 +459,16 @@ final class TestStoreTests: BaseTCATestCase {
|
||||
$0 = 1
|
||||
}
|
||||
} issueMatcher: {
|
||||
$0.compactDescription == """
|
||||
failed - A state change does not match expectation: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
A state change does not match expectation.
|
||||
|
||||
− 1
|
||||
+ 0
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,8 +553,9 @@ final class TestStoreTests: BaseTCATestCase {
|
||||
await store.receive(\.delegate.success, 42)
|
||||
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Received unexpected action: …
|
||||
$0.compactDescription.hasSuffix(
|
||||
"""
|
||||
Received unexpected action:
|
||||
|
||||
Action.delegate(
|
||||
− .success(43)
|
||||
@@ -561,6 +564,7 @@ final class TestStoreTests: BaseTCATestCase {
|
||||
|
||||
(Expected: −, Actual: +)
|
||||
"""
|
||||
)
|
||||
}
|
||||
await store.send(.tap)
|
||||
await store.receive(\.delegate.success, 43)
|
||||
@@ -652,9 +656,7 @@ final class TestStoreTests: BaseTCATestCase {
|
||||
}
|
||||
await store.send(.dismiss)
|
||||
XCTExpectFailure {
|
||||
$0.compactDescription == """
|
||||
failed - Can't send action to dismissed test store.
|
||||
"""
|
||||
$0.compactDescription.hasSuffix("Can't send action to dismissed test store.")
|
||||
}
|
||||
await store.send(.onTask)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user