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