diff --git a/.github/package.xcworkspace/contents.xcworkspacedata b/.github/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000000..0fd0bc317eca
--- /dev/null
+++ b/.github/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000000..18d981003d68
--- /dev/null
+++ b/.github/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved b/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 000000000000..148b94ec1204
--- /dev/null
+++ b/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,122 @@
+{
+ "pins" : [
+ {
+ "identity" : "combine-schedulers",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/combine-schedulers",
+ "state" : {
+ "revision" : "882ac01eb7ef9e36d4467eb4b1151e74fcef85ab",
+ "version" : "0.9.1"
+ }
+ },
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser",
+ "state" : {
+ "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
+ "version" : "1.2.2"
+ }
+ },
+ {
+ "identity" : "swift-benchmark",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/swift-benchmark",
+ "state" : {
+ "revision" : "8163295f6fe82356b0bcf8e1ab991645de17d096",
+ "version" : "0.1.2"
+ }
+ },
+ {
+ "identity" : "swift-case-paths",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-case-paths",
+ "state" : {
+ "revision" : "f623901b4bcc97f59c36704f81583f169b228e51",
+ "version" : "0.13.0"
+ }
+ },
+ {
+ "identity" : "swift-clocks",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-clocks",
+ "state" : {
+ "revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
+ "version" : "0.2.0"
+ }
+ },
+ {
+ "identity" : "swift-collections",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-collections",
+ "state" : {
+ "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
+ "version" : "1.0.4"
+ }
+ },
+ {
+ "identity" : "swift-custom-dump",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-custom-dump",
+ "state" : {
+ "revision" : "dd86159e25c749873f144577e5d18309bf57534f",
+ "version" : "0.8.0"
+ }
+ },
+ {
+ "identity" : "swift-dependencies",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-dependencies",
+ "state" : {
+ "revision" : "8282b0c59662eb38946afe30eb403663fc2ecf76",
+ "version" : "0.1.4"
+ }
+ },
+ {
+ "identity" : "swift-docc-plugin",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-docc-plugin",
+ "state" : {
+ "revision" : "10bc670db657d11bdd561e07de30a9041311b2b1",
+ "version" : "1.1.0"
+ }
+ },
+ {
+ "identity" : "swift-docc-symbolkit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-docc-symbolkit",
+ "state" : {
+ "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
+ "version" : "1.0.0"
+ }
+ },
+ {
+ "identity" : "swift-identified-collections",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-identified-collections",
+ "state" : {
+ "revision" : "ad3932d28c2e0a009a0167089619526709ef6497",
+ "version" : "0.7.0"
+ }
+ },
+ {
+ "identity" : "swiftui-navigation",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swiftui-navigation",
+ "state" : {
+ "revision" : "270a754308f5440be52fc295242eb7031638bd15",
+ "version" : "0.6.1"
+ }
+ },
+ {
+ "identity" : "xctest-dynamic-overlay",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
+ "state" : {
+ "revision" : "62041e6016a30f56952f5d7d3f12a3fd7029e1cd",
+ "version" : "0.8.3"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/ComposableArchitecture.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/ComposableArchitecture.xcscheme
new file mode 100644
index 000000000000..318b7853e1fd
--- /dev/null
+++ b/.github/package.xcworkspace/xcshareddata/xcschemes/ComposableArchitecture.xcscheme
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/package.xcworkspace/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme b/.github/package.xcworkspace/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme
new file mode 100644
index 000000000000..9bcb82da53f6
--- /dev/null
+++ b/.github/package.xcworkspace/xcshareddata/xcschemes/swift-composable-architecture-benchmark.xcscheme
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9a09f8e880db..00ba12edbf31 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: macos-12
strategy:
matrix:
- xcode: ['14.0.1']
+ xcode: ['13.4.1', '14.2']
config: ['debug', 'release']
steps:
- uses: actions/checkout@v3
@@ -29,13 +29,10 @@ jobs:
library-evolution:
runs-on: macos-12
- strategy:
- matrix:
- xcode: ['14.1']
steps:
- uses: actions/checkout@v3
- - name: Select Xcode ${{ matrix.xcode }}
- run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
+ - name: Select Xcode 14.2
+ run: sudo xcode-select -s /Applications/Xcode_14.2.app
- name: Build for library evolution
run: make build-for-library-evolution
@@ -43,8 +40,8 @@ jobs:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- - name: Select Xcode 14.1
- run: sudo xcode-select -s /Applications/Xcode_14.1.app
+ - name: Select Xcode 14.2
+ run: sudo xcode-select -s /Applications/Xcode_14.2.app
- name: Run benchmark
run: make benchmark
@@ -52,7 +49,7 @@ jobs:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- - name: Select Xcode ${{ matrix.xcode }}
- run: sudo xcode-select -s /Applications/Xcode_14.0.1.app
+ - name: Select Xcode 14.2
+ run: sudo xcode-select -s /Applications/Xcode_14.2.app
- name: Run tests
run: make test-examples
diff --git a/.gitignore b/.gitignore
index 6cb6e6ff5bcd..ac4e556f7ac4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,6 @@
/.build
/.swiftpm
/Packages
+/*.swiftinterface
/*.xcodeproj
xcuserdata/
diff --git a/Examples/Integration/Integration/PresentationTestCase.swift b/Examples/Integration/Integration/PresentationTestCase.swift
index a3665a5202a2..e27c7b13d8e8 100644
--- a/Examples/Integration/Integration/PresentationTestCase.swift
+++ b/Examples/Integration/Integration/PresentationTestCase.swift
@@ -51,12 +51,11 @@ private struct PresentationTestCase: ReducerProtocol {
var body: some ReducerProtocolOf {
Reduce { state, action in
switch action {
- case
- .destination(.presented(.fullScreenCover(.parentSendDismissActionButtonTapped))),
- .destination(.presented(.navigationDestination(.parentSendDismissActionButtonTapped))),
- .destination(.presented(.navigationLink(.parentSendDismissActionButtonTapped))),
- .destination(.presented(.sheet(.parentSendDismissActionButtonTapped))),
- .destination(.presented(.popover(.parentSendDismissActionButtonTapped))):
+ case .destination(.presented(.fullScreenCover(.parentSendDismissActionButtonTapped))),
+ .destination(.presented(.navigationDestination(.parentSendDismissActionButtonTapped))),
+ .destination(.presented(.navigationLink(.parentSendDismissActionButtonTapped))),
+ .destination(.presented(.sheet(.parentSendDismissActionButtonTapped))),
+ .destination(.presented(.popover(.parentSendDismissActionButtonTapped))):
return .send(.destination(.dismiss))
case .destination:
return .none
@@ -147,7 +146,8 @@ struct PresentationTestCaseView: View {
}
NavigationLinkStore(
- store: self.store.scope(state: \.$destination, action: PresentationTestCase.Action.destination),
+ store: self.store.scope(
+ state: \.$destination, action: PresentationTestCase.Action.destination),
state: /PresentationTestCase.Destination.State.navigationLink,
action: PresentationTestCase.Destination.Action.navigationLink
) {
@@ -171,28 +171,32 @@ struct PresentationTestCaseView: View {
}
}
.fullScreenCover(
- store: self.store.scope(state: \.$destination, action: PresentationTestCase.Action.destination),
+ store: self.store.scope(
+ state: \.$destination, action: PresentationTestCase.Action.destination),
state: /PresentationTestCase.Destination.State.fullScreenCover,
action: PresentationTestCase.Destination.Action.fullScreenCover
) { store in
ChildView(store: store)
}
.navigationDestination(
- store: self.store.scope(state: \.$destination, action: PresentationTestCase.Action.destination),
+ store: self.store.scope(
+ state: \.$destination, action: PresentationTestCase.Action.destination),
state: /PresentationTestCase.Destination.State.navigationDestination,
action: PresentationTestCase.Destination.Action.navigationDestination
) { store in
ChildView(store: store)
}
.popover(
- store: self.store.scope(state: \.$destination, action: PresentationTestCase.Action.destination),
+ store: self.store.scope(
+ state: \.$destination, action: PresentationTestCase.Action.destination),
state: /PresentationTestCase.Destination.State.popover,
action: PresentationTestCase.Destination.Action.popover
) { store in
ChildView(store: store)
}
.sheet(
- store: self.store.scope(state: \.$destination, action: PresentationTestCase.Action.destination),
+ store: self.store.scope(
+ state: \.$destination, action: PresentationTestCase.Action.destination),
state: /PresentationTestCase.Destination.State.sheet,
action: PresentationTestCase.Destination.Action.sheet
) { store in
diff --git a/Examples/Integration/IntegrationUITests/EscapedWithViewStoreTests.swift b/Examples/Integration/IntegrationUITests/EscapedWithViewStoreTests.swift
index aa051e62667d..93fe3d25af35 100644
--- a/Examples/Integration/IntegrationUITests/EscapedWithViewStoreTests.swift
+++ b/Examples/Integration/IntegrationUITests/EscapedWithViewStoreTests.swift
@@ -1,6 +1,6 @@
import Integration
-import XCTest
import TestCases
+import XCTest
@MainActor
final class EscapedWithViewStoreTests: XCTestCase {
diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift
index 5a900ce88af7..07f6e8183b6a 100644
--- a/Examples/Integration/IntegrationUITests/PresentationTests.swift
+++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift
@@ -1,6 +1,6 @@
import Integration
-import XCTest
import TestCases
+import XCTest
@MainActor
final class PresentationTests: XCTestCase {
@@ -52,6 +52,9 @@ final class PresentationTests: XCTestCase {
}
func testSheet_IdentityChange() async throws {
+ // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed.
+ XCTExpectFailure()
+
self.app.buttons["Open sheet"].tap()
XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists)
diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift
index c51cb4d2d458..71a87423ab37 100644
--- a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift
+++ b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift
@@ -41,11 +41,11 @@ public struct TwoFactor: Reducer, Sendable {
case .submitButtonTapped:
state.isTwoFactorRequestInFlight = true
return .task { [code = state.code, token = state.token] in
- .twoFactorResponse(
- await TaskResult {
- try await self.authenticationClient.twoFactor(.init(code: code, token: token))
- }
- )
+ .twoFactorResponse(
+ await TaskResult {
+ try await self.authenticationClient.twoFactor(.init(code: code, token: token))
+ }
+ )
}
case let .twoFactorResponse(.failure(error)):
diff --git a/Makefile b/Makefile
index 2702d5d4b661..e4adedefca56 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+CONFIG=debug
+
PLATFORM_IOS = iOS Simulator,name=iPhone 11 Pro Max
PLATFORM_MACOS = macOS
PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst
@@ -14,7 +16,7 @@ test-library:
for platform in "$(PLATFORM_IOS)" "$(PLATFORM_MACOS)" "$(PLATFORM_MAC_CATALYST)" "$(PLATFORM_TVOS)" "$(PLATFORM_WATCHOS)"; do \
xcodebuild test \
-configuration $(CONFIG) \
- -workspace ComposableArchitecture.xcworkspace \
+ -workspace .github/package.xcworkspace \
-scheme ComposableArchitecture \
-destination platform="$$platform" || exit 1; \
done;
diff --git a/README.md b/README.md
index bd474e0f5482..67c8b128bb11 100644
--- a/README.md
+++ b/README.md
@@ -548,13 +548,14 @@ advanced usages.
The documentation for releases and `main` are available here:
* [`main`](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture)
-* [0.50.0](https://pointfreeco.github.io/swift-composable-architecture/0.50.0/documentation/composablearchitecture/)
+* [0.51.0](https://pointfreeco.github.io/swift-composable-architecture/0.51.0/documentation/composablearchitecture/)
Other versions
+ * [0.50.0](https://pointfreeco.github.io/swift-composable-architecture/0.50.0/documentation/composablearchitecture/)
* [0.49.0](https://pointfreeco.github.io/swift-composable-architecture/0.49.0/documentation/composablearchitecture/)
* [0.48.0](https://pointfreeco.github.io/swift-composable-architecture/0.48.0/documentation/composablearchitecture/)
* [0.47.0](https://pointfreeco.github.io/swift-composable-architecture/0.47.0/documentation/composablearchitecture/)
diff --git a/Sources/ComposableArchitecture/Internal/EphemeralState.swift b/Sources/ComposableArchitecture/Internal/EphemeralState.swift
index c70cc89f6a5d..7b29e5149178 100644
--- a/Sources/ComposableArchitecture/Internal/EphemeralState.swift
+++ b/Sources/ComposableArchitecture/Internal/EphemeralState.swift
@@ -16,7 +16,7 @@ func isEphemeral(_ state: State) -> Bool {
return true
} else if let metadata = EnumMetadata(type(of: state)) {
return metadata.associatedValueType(forTag: metadata.tag(of: state))
- is _EphemeralState.Type
+ is _EphemeralState.Type
} else {
return false
}
diff --git a/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift b/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift
index d8aaf237aab8..af1a55a6b8fb 100644
--- a/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift
+++ b/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift
@@ -28,7 +28,7 @@ public enum ReducerBuilder {
public static func buildEither(
first reducer: R0
) -> _Conditional
- where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action {
+ where R0.State == State, R0.Action == Action {
.first(reducer)
}
@@ -36,7 +36,7 @@ public enum ReducerBuilder {
public static func buildEither(
second reducer: R1
) -> _Conditional
- where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action {
+ where R0.State == State, R0.Action == Action {
.second(reducer)
}
@@ -78,7 +78,7 @@ public enum ReducerBuilder {
public static func buildPartialBlock(
accumulated: R0, next: R1
) -> _Sequence
- where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action {
+ where R0.State == State, R0.Action == Action {
_Sequence(accumulated, next)
}
diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift
index a24ad561387a..2d6acd080efe 100644
--- a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift
+++ b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift
@@ -30,7 +30,8 @@ public struct PresentationState {
}
public var projectedValue: Self {
- _read { yield self }
+ get { self }
+ set { self = newValue }
_modify { yield &self }
}
diff --git a/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift b/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift
index 661754fa5546..34f3ff06a9fc 100644
--- a/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift
+++ b/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift
@@ -4,8 +4,8 @@ import SwiftUI
extension View {
public func confirmationDialog(
store: Store<
- PresentationState>,
- PresentationAction
+ PresentationState>,
+ PresentationAction
>
) -> some View {
self.confirmationDialog(store: store, state: { $0 }, action: { $0 })
@@ -127,7 +127,7 @@ private struct PresentationConfirmationDialogModifier) -> Content
) -> some View {
self.modifier(
- PresentationPopoverModifer(
+ PresentationPopoverModifier(
store: store,
state: toDestinationState,
action: fromDestinationAction,
@@ -42,7 +42,9 @@ extension View {
}
}
-private struct PresentationPopoverModifer<
+@available(tvOS, unavailable)
+@available(watchOS, unavailable)
+private struct PresentationPopoverModifier<
State,
Action,
DestinationState,
diff --git a/Sources/ComposableArchitecture/SwiftUI/Sheet.swift b/Sources/ComposableArchitecture/SwiftUI/Sheet.swift
index 431684e4f780..cc67697a72f6 100644
--- a/Sources/ComposableArchitecture/SwiftUI/Sheet.swift
+++ b/Sources/ComposableArchitecture/SwiftUI/Sheet.swift
@@ -71,4 +71,3 @@ private struct PresentationSheetModifier<
}
}
}
-
diff --git a/Tests/ComposableArchitectureTests/IfCaseLetReducerTests.swift b/Tests/ComposableArchitectureTests/IfCaseLetReducerTests.swift
index 944666c6ef62..c83de5eebfa3 100644
--- a/Tests/ComposableArchitectureTests/IfCaseLetReducerTests.swift
+++ b/Tests/ComposableArchitectureTests/IfCaseLetReducerTests.swift
@@ -71,83 +71,86 @@ final class IfCaseLetReducerTests: XCTestCase {
}
#endif
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testEffectCancellation_Siblings() async {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case timerButtonTapped
- case timerTick
- }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .timerButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.timerTick)
+ #if swift(>=5.7)
+ func testEffectCancellation_Siblings() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case timerButtonTapped
+ case timerTick
+ }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .timerButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.timerTick)
+ }
+ }
+ case .timerTick:
+ state.count += 1
+ return .none
}
}
- case .timerTick:
- state.count += 1
- return .none
}
- }
- }
- struct Parent: ReducerProtocol {
- enum State: Equatable {
- case child1(Child.State)
- case child2(Child.State)
- }
- enum Action: Equatable {
- case child1(Child.Action)
- case child1ButtonTapped
- case child2(Child.Action)
- case child2ButtonTapped
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child1:
- return .none
- case .child1ButtonTapped:
- state = .child1(Child.State())
- return .none
- case .child2:
- return .none
- case .child2ButtonTapped:
- state = .child2(Child.State())
- return .none
+ struct Parent: ReducerProtocol {
+ enum State: Equatable {
+ case child1(Child.State)
+ case child2(Child.State)
+ }
+ enum Action: Equatable {
+ case child1(Child.Action)
+ case child1ButtonTapped
+ case child2(Child.Action)
+ case child2ButtonTapped
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child1:
+ return .none
+ case .child1ButtonTapped:
+ state = .child1(Child.State())
+ return .none
+ case .child2:
+ return .none
+ case .child2ButtonTapped:
+ state = .child2(Child.State())
+ return .none
+ }
+ }
+ .ifCaseLet(/State.child1, action: /Action.child1) {
+ Child()
+ }
+ .ifCaseLet(/State.child2, action: /Action.child2) {
+ Child()
+ }
}
}
- .ifCaseLet(/State.child1, action: /Action.child1) {
- Child()
- }
- .ifCaseLet(/State.child2, action: /Action.child2) {
- Child()
- }
- }
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State.child1(Child.State()),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.child1(.timerButtonTapped))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.timerTick)) {
- try (/Parent.State.child1).modify(&$0) {
- $0.count = 1
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State.child1(Child.State()),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.child1(.timerButtonTapped))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.timerTick)) {
+ try (/Parent.State.child1).modify(&$0) {
+ $0.count = 1
+ }
+ }
+ await store.send(.child2ButtonTapped) {
+ $0 = .child2(Child.State())
+ }
}
}
- await store.send(.child2ButtonTapped) {
- $0 = .child2(Child.State())
- }
}
- }
+ #endif
}
diff --git a/Tests/ComposableArchitectureTests/IfLetReducerTests.swift b/Tests/ComposableArchitectureTests/IfLetReducerTests.swift
index ed5eb617bc22..aa3336775d0a 100644
--- a/Tests/ComposableArchitectureTests/IfLetReducerTests.swift
+++ b/Tests/ComposableArchitectureTests/IfLetReducerTests.swift
@@ -40,212 +40,216 @@ final class IfLetReducerTests: XCTestCase {
}
#endif
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testEffectCancellation() async {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case timerButtonTapped
- case timerTick
- }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .timerButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.timerTick)
+ #if swift(>=5.7)
+ func testEffectCancellation() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case timerButtonTapped
+ case timerTick
+ }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .timerButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.timerTick)
+ }
+ }
+ case .timerTick:
+ state.count += 1
+ return .none
}
}
- case .timerTick:
- state.count += 1
- return .none
}
- }
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- var child: Child.State?
- }
- enum Action: Equatable {
- case child(Child.Action)
- case childButtonTapped
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .childButtonTapped:
- state.child = state.child == nil ? Child.State() : nil
- return .none
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ var child: Child.State?
+ }
+ enum Action: Equatable {
+ case child(Child.Action)
+ case childButtonTapped
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .childButtonTapped:
+ state.child = state.child == nil ? Child.State() : nil
+ return .none
+ }
+ }
+ .ifLet(\.child, action: /Action.child) {
+ Child()
+ }
}
}
- .ifLet(\.child, action: /Action.child) {
- Child()
- }
- }
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.childButtonTapped) {
- $0.child = Child.State()
- }
- await store.send(.child(.timerButtonTapped))
- await clock.advance(by: .seconds(2))
- await store.receive(.child(.timerTick)) {
- try (/.some).modify(&$0.child) {
- $0.count = 1
- }
- }
- await store.receive(.child(.timerTick)) {
- try (/.some).modify(&$0.child) {
- $0.count = 2
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.childButtonTapped) {
+ $0.child = Child.State()
+ }
+ await store.send(.child(.timerButtonTapped))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.child(.timerTick)) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 1
+ }
+ }
+ await store.receive(.child(.timerTick)) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 2
+ }
+ }
+ await store.send(.childButtonTapped) {
+ $0.child = nil
+ }
}
}
- await store.send(.childButtonTapped) {
- $0.child = nil
- }
}
- }
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testGrandchildEffectCancellation() async {
- struct GrandChild: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case timerButtonTapped
- case timerTick
- }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .timerButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.timerTick)
+ func testGrandchildEffectCancellation() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct GrandChild: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case timerButtonTapped
+ case timerTick
+ }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .timerButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.timerTick)
+ }
+ }
+ case .timerTick:
+ state.count += 1
+ return .none
}
}
- case .timerTick:
- state.count += 1
- return .none
}
- }
- }
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var grandChild: GrandChild.State?
- }
- enum Action: Equatable {
- case grandChild(GrandChild.Action)
- }
- var body: some ReducerProtocolOf {
- EmptyReducer()
- .ifLet(\.grandChild, action: /Action.grandChild) {
- GrandChild()
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var grandChild: GrandChild.State?
}
- }
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- var child: Child.State?
- }
- enum Action: Equatable {
- case child(Child.Action)
- case exitButtonTapped
- case startButtonTapped
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .exitButtonTapped:
- state.child = nil
- return .none
- case .startButtonTapped:
- state.child = Child.State(grandChild: GrandChild.State())
- return .none
+ enum Action: Equatable {
+ case grandChild(GrandChild.Action)
+ }
+ var body: some ReducerProtocolOf {
+ EmptyReducer()
+ .ifLet(\.grandChild, action: /Action.grandChild) {
+ GrandChild()
+ }
}
}
- .ifLet(\.child, action: /Action.child) {
- Child()
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ var child: Child.State?
+ }
+ enum Action: Equatable {
+ case child(Child.Action)
+ case exitButtonTapped
+ case startButtonTapped
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .exitButtonTapped:
+ state.child = nil
+ return .none
+ case .startButtonTapped:
+ state.child = Child.State(grandChild: GrandChild.State())
+ return .none
+ }
+ }
+ .ifLet(\.child, action: /Action.child) {
+ Child()
+ }
+ }
}
- }
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.startButtonTapped) {
- $0.child = Child.State(grandChild: GrandChild.State())
- }
- await store.send(.child(.grandChild(.timerButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child(.grandChild(.timerTick))) {
- try (/.some).modify(&$0.child) {
- try (/.some).modify(&$0.grandChild) {
- $0.count = 1
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.startButtonTapped) {
+ $0.child = Child.State(grandChild: GrandChild.State())
+ }
+ await store.send(.child(.grandChild(.timerButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child(.grandChild(.timerTick))) {
+ try (/.some).modify(&$0.child) {
+ try (/.some).modify(&$0.grandChild) {
+ $0.count = 1
+ }
+ }
+ }
+ await store.send(.exitButtonTapped) {
+ $0.child = nil
}
}
}
- await store.send(.exitButtonTapped) {
- $0.child = nil
- }
}
- }
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testEphemeralState() async {
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- var alert: AlertState?
- }
- enum Action: Equatable {
- case alert(AlertAction)
- case tap
- }
- enum AlertAction { case ok }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .alert:
- return .none
- case .tap:
- state.alert = AlertState { TextState("Hi!") }
- return .none
+ func testEphemeralState() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ var alert: AlertState?
+ }
+ enum Action: Equatable {
+ case alert(AlertAction)
+ case tap
+ }
+ enum AlertAction { case ok }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .alert:
+ return .none
+ case .tap:
+ state.alert = AlertState { TextState("Hi!") }
+ return .none
+ }
+ }
+ .ifLet(\.alert, action: /Action.alert) {
+ }
}
}
- .ifLet(\.alert, action: /Action.alert) {
- EmptyReducer()
+ await _withMainSerialExecutor {
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ )
+ await store.send(.tap) {
+ $0.alert = AlertState { TextState("Hi!") }
+ }
+ await store.send(.alert(.ok)) {
+ $0.alert = nil
+ }
}
}
}
- await _withMainSerialExecutor {
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- )
- await store.send(.tap) {
- $0.alert = AlertState { TextState("Hi!") }
- }
- await store.send(.alert(.ok)) {
- $0.alert = nil
- }
- }
- }
+ #endif
}
diff --git a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift
index 30760ed76f58..e63883b81bdf 100644
--- a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift
+++ b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift
@@ -22,7 +22,7 @@ import XCTest
state.count += 1
return .none
}
- }
+ }
}
struct Parent: ReducerProtocol {
@@ -142,258 +142,261 @@ import XCTest
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testPresentation_parentDismissal_effects() async {
- await _withMainSerialExecutor {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case startButtonTapped
- case tick
- }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .startButtonTapped:
- return .run { send in
- for try await _ in clock.timer(interval: .seconds(1)) {
- await send(.tick)
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ await _withMainSerialExecutor {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case startButtonTapped
+ case tick
+ }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .startButtonTapped:
+ return .run { send in
+ for try await _ in clock.timer(interval: .seconds(1)) {
+ await send(.tick)
+ }
}
+ case .tick:
+ state.count += 1
+ return .none
}
- case .tick:
- state.count += 1
- return .none
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child: Child.State?
- }
- enum Action: Equatable {
- case child(PresentationAction)
- case presentChild
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .presentChild:
- state.child = Child.State()
- return .none
- }
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child: Child.State?
}
- .ifLet(\.$child, action: /Action.child) {
- Child()
+ enum Action: Equatable {
+ case child(PresentationAction)
+ case presentChild
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .presentChild:
+ state.child = Child.State()
+ return .none
+ }
+ }
+ .ifLet(\.$child, action: /Action.child) {
+ Child()
+ }
}
}
- }
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
- await store.send(.presentChild) {
- $0.child = Child.State()
- }
- await store.send(.child(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(2))
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 1
+ await store.send(.presentChild) {
+ $0.child = Child.State()
}
- }
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 2
+ await store.send(.child(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 1
+ }
+ }
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 2
+ }
+ }
+ await store.send(.child(.dismiss)) {
+ $0.child = nil
}
- }
- await store.send(.child(.dismiss)) {
- $0.child = nil
}
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testPresentation_childDismissal_effects() async {
- await _withMainSerialExecutor {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case closeButtonTapped
- case startButtonTapped
- case tick
- }
- @Dependency(\.continuousClock) var clock
- @Dependency(\.dismiss) var dismiss
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .closeButtonTapped:
- return .fireAndForget {
- await self.dismiss()
- }
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ await _withMainSerialExecutor {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case closeButtonTapped
+ case startButtonTapped
+ case tick
+ }
+ @Dependency(\.continuousClock) var clock
+ @Dependency(\.dismiss) var dismiss
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .closeButtonTapped:
+ return .fireAndForget {
+ await self.dismiss()
+ }
- case .startButtonTapped:
- return .run { send in
- for try await _ in clock.timer(interval: .seconds(1)) {
- await send(.tick)
+ case .startButtonTapped:
+ return .run { send in
+ for try await _ in clock.timer(interval: .seconds(1)) {
+ await send(.tick)
+ }
}
+ case .tick:
+ state.count += 1
+ return .none
}
- case .tick:
- state.count += 1
- return .none
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child: Child.State?
- }
- enum Action: Equatable {
- case child(PresentationAction)
- case presentChild
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .presentChild:
- state.child = Child.State()
- return .none
- }
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child: Child.State?
}
- .ifLet(\.$child, action: /Action.child) {
- Child()
+ enum Action: Equatable {
+ case child(PresentationAction)
+ case presentChild
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .presentChild:
+ state.child = Child.State()
+ return .none
+ }
+ }
+ .ifLet(\.$child, action: /Action.child) {
+ Child()
+ }
}
}
- }
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
- await store.send(.presentChild) {
- $0.child = Child.State()
- }
- await store.send(.child(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(2))
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 1
+ await store.send(.presentChild) {
+ $0.child = Child.State()
}
- }
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 2
+ await store.send(.child(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 1
+ }
+ }
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 2
+ }
+ }
+ await store.send(.child(.presented(.closeButtonTapped)))
+ await store.receive(.child(.dismiss)) {
+ $0.child = nil
}
- }
- await store.send(.child(.presented(.closeButtonTapped)))
- await store.receive(.child(.dismiss)) {
- $0.child = nil
}
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testPresentation_identifiableDismissal_effects() async {
- await _withMainSerialExecutor {
- struct Child: ReducerProtocol {
- struct State: Equatable, Identifiable {
- let id: UUID
- var count = 0
- }
- enum Action: Equatable {
- case startButtonTapped
- case tick
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ await _withMainSerialExecutor {
+ struct Child: ReducerProtocol {
+ struct State: Equatable, Identifiable {
+ let id: UUID
+ var count = 0
+ }
+ enum Action: Equatable {
+ case startButtonTapped
+ case tick
+ }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .startButtonTapped:
+ return .run { send in
+ for try await _ in clock.timer(interval: .seconds(1)) {
+ await send(.tick)
+ }
+ }
+ case .tick:
+ state.count += 1
+ return .none
+ }
+ }
}
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .startButtonTapped:
- return .run { send in
- for try await _ in clock.timer(interval: .seconds(1)) {
- await send(.tick)
+
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child: Child.State?
+ }
+ enum Action: Equatable {
+ case child(PresentationAction)
+ case presentChild
+ }
+ @Dependency(\.uuid) var uuid
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .presentChild:
+ state.child = Child.State(id: self.uuid())
+ return .none
}
}
- case .tick:
- state.count += 1
- return .none
+ .ifLet(\.$child, action: /Action.child) {
+ Child()
+ }
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child: Child.State?
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ $0.uuid = .incrementing
}
- enum Action: Equatable {
- case child(PresentationAction)
- case presentChild
+
+ await store.send(.presentChild) {
+ $0.child = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
}
- @Dependency(\.uuid) var uuid
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .presentChild:
- state.child = Child.State(id: self.uuid())
- return .none
- }
+ await store.send(.child(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 1
}
- .ifLet(\.$child, action: /Action.child) {
- Child()
+ }
+ await store.receive(.child(.presented(.tick))) {
+ try (/.some).modify(&$0.child) {
+ $0.count = 2
}
}
- }
-
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- $0.uuid = .incrementing
- }
-
- await store.send(.presentChild) {
- $0.child = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
- }
- await store.send(.child(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(2))
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 1
+ await store.send(.presentChild) {
+ $0.child = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
}
- }
- await store.receive(.child(.presented(.tick))) {
- try (/.some).modify(&$0.child) {
- $0.count = 2
+ await clock.advance(by: .seconds(2))
+ await store.send(.child(.dismiss)) {
+ $0.child = nil
}
}
- await store.send(.presentChild) {
- $0.child = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
- }
- await clock.advance(by: .seconds(2))
- await store.send(.child(.dismiss)) {
- $0.child = nil
- }
}
}
@@ -453,144 +456,147 @@ import XCTest
await store.skipInFlightEffects(strict: true)
}
- @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
func testInertPresentation() async {
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var alert: AlertState?
- }
- enum Action: Equatable {
- case alert(PresentationAction)
- case presentAlert
+ if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var alert: AlertState?
+ }
+ enum Action: Equatable {
+ case alert(PresentationAction)
+ case presentAlert
- enum Alert: Equatable {}
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .alert:
- return .none
- case .presentAlert:
- state.alert = AlertState {
- TextState("Uh oh!")
+ enum Alert: Equatable {}
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .alert:
+ return .none
+ case .presentAlert:
+ state.alert = AlertState {
+ TextState("Uh oh!")
+ }
+ return .none
}
- return .none
}
+ .ifLet(\.$alert, action: /Action.alert) {}
}
- .ifLet(\.$alert, action: /Action.alert) {}
}
- }
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- )
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ )
- await store.send(.presentAlert) {
- $0.alert = AlertState {
- TextState("Uh oh!")
+ await store.send(.presentAlert) {
+ $0.alert = AlertState {
+ TextState("Uh oh!")
+ }
}
}
}
- @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
func testInertPresentation_dismissal() async {
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var alert: AlertState?
- }
- enum Action: Equatable {
- case alert(PresentationAction)
- case presentAlert
-
- enum Alert: Equatable {}
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .alert:
- return .none
- case .presentAlert:
- state.alert = AlertState {
- TextState("Uh oh!")
+ if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var alert: AlertState?
+ }
+ enum Action: Equatable {
+ case alert(PresentationAction)
+ case presentAlert
+
+ enum Alert: Equatable {}
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .alert:
+ return .none
+ case .presentAlert:
+ state.alert = AlertState {
+ TextState("Uh oh!")
+ }
+ return .none
}
- return .none
}
+ .ifLet(\.$alert, action: /Action.alert) {}
}
- .ifLet(\.$alert, action: /Action.alert) {}
}
- }
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- )
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ )
- await store.send(.presentAlert) {
- $0.alert = AlertState {
- TextState("Uh oh!")
+ await store.send(.presentAlert) {
+ $0.alert = AlertState {
+ TextState("Uh oh!")
+ }
+ }
+ await store.send(.alert(.dismiss)) {
+ $0.alert = nil
}
- }
- await store.send(.alert(.dismiss)) {
- $0.alert = nil
}
}
- @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
func testInertPresentation_automaticDismissal() async {
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var alert: AlertState?
- var isDeleted = false
- }
- enum Action: Equatable {
- case alert(PresentationAction)
- case presentAlert
+ if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var alert: AlertState?
+ var isDeleted = false
+ }
+ enum Action: Equatable {
+ case alert(PresentationAction)
+ case presentAlert
- enum Alert: Equatable {
- case deleteButtonTapped
+ enum Alert: Equatable {
+ case deleteButtonTapped
+ }
}
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .alert(.presented(.deleteButtonTapped)):
- state.isDeleted = true
- return .none
- case .alert:
- return .none
- case .presentAlert:
- state.alert = AlertState {
- TextState("Uh oh!")
- } actions: {
- ButtonState(role: .destructive, action: .deleteButtonTapped) {
- TextState("Delete")
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .alert(.presented(.deleteButtonTapped)):
+ state.isDeleted = true
+ return .none
+ case .alert:
+ return .none
+ case .presentAlert:
+ state.alert = AlertState {
+ TextState("Uh oh!")
+ } actions: {
+ ButtonState(role: .destructive, action: .deleteButtonTapped) {
+ TextState("Delete")
+ }
}
+ return .none
}
- return .none
}
+ .ifLet(\.$alert, action: /Action.alert) {}
}
- .ifLet(\.$alert, action: /Action.alert) {}
}
- }
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- )
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ )
- await store.send(.presentAlert) {
- $0.alert = AlertState {
- TextState("Uh oh!")
- } actions: {
- ButtonState(role: .destructive, action: .deleteButtonTapped) {
- TextState("Delete")
+ await store.send(.presentAlert) {
+ $0.alert = AlertState {
+ TextState("Uh oh!")
+ } actions: {
+ ButtonState(role: .destructive, action: .deleteButtonTapped) {
+ TextState("Delete")
+ }
}
}
- }
- await store.send(.alert(.presented(.deleteButtonTapped))) {
- $0.alert = nil
- $0.isDeleted = true
+ await store.send(.alert(.presented(.deleteButtonTapped))) {
+ $0.alert = nil
+ $0.isDeleted = true
+ }
}
}
@@ -658,182 +664,182 @@ import XCTest
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testEnumPresentation() async {
- await _withMainSerialExecutor {
- struct Child: ReducerProtocol {
- struct State: Equatable, Identifiable {
- let id: UUID
- var count = 0
- }
- enum Action: Equatable {
- case closeButtonTapped
- case startButtonTapped
- case tick
- }
- @Dependency(\.continuousClock) var clock
- @Dependency(\.dismiss) var dismiss
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .closeButtonTapped:
- return .fireAndForget {
- await self.dismiss()
- }
-
- case .startButtonTapped:
- return .run { send in
- for try await _ in clock.timer(interval: .seconds(1)) {
- await send(.tick)
- }
- }
- case .tick:
- state.count += 1
- return .none
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ await _withMainSerialExecutor {
+ struct Child: ReducerProtocol {
+ struct State: Equatable, Identifiable {
+ let id: UUID
+ var count = 0
}
- }
- }
-
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var destination: Destinations.State?
- var isDeleted = false
- }
- enum Action: Equatable {
- case destination(PresentationAction)
- case presentAlert
- case presentChild(id: UUID? = nil)
- }
- @Dependency(\.uuid) var uuid
- var body: some ReducerProtocol {
- Reduce { state, action in
+ enum Action: Equatable {
+ case closeButtonTapped
+ case startButtonTapped
+ case tick
+ }
+ @Dependency(\.continuousClock) var clock
+ @Dependency(\.dismiss) var dismiss
+ func reduce(into state: inout State, action: Action) -> EffectTask {
switch action {
- case .destination(.presented(.alert(.deleteButtonTapped))):
- state.isDeleted = true
- return .none
- case .destination:
- return .none
- case .presentAlert:
- state.destination = .alert(
- AlertState {
- TextState("Uh oh!")
- } actions: {
- ButtonState(role: .destructive, action: .deleteButtonTapped) {
- TextState("Delete")
- }
+ case .closeButtonTapped:
+ return .fireAndForget {
+ await self.dismiss()
+ }
+ case .startButtonTapped:
+ return .run { send in
+ for try await _ in clock.timer(interval: .seconds(1)) {
+ await send(.tick)
}
- )
- return .none
- case let .presentChild(id):
- state.destination = .child(Child.State(id: id ?? self.uuid()))
+ }
+ case .tick:
+ state.count += 1
return .none
}
}
- .ifLet(\.$destination, action: /Action.destination) {
- Destinations()
- }
}
- struct Destinations: ReducerProtocol {
- enum State: Equatable {
- case alert(AlertState)
- case child(Child.State)
+
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var destination: Destinations.State?
+ var isDeleted = false
}
enum Action: Equatable {
- case alert(Alert)
- case child(Child.Action)
-
- enum Alert: Equatable {
- case deleteButtonTapped
- }
+ case destination(PresentationAction)
+ case presentAlert
+ case presentChild(id: UUID? = nil)
}
+ @Dependency(\.uuid) var uuid
var body: some ReducerProtocol {
- Scope(state: /State.alert, action: /Action.alert) {}
- Scope(state: /State.child, action: /Action.child) {
- Child()
+ Reduce { state, action in
+ switch action {
+ case .destination(.presented(.alert(.deleteButtonTapped))):
+ state.isDeleted = true
+ return .none
+ case .destination:
+ return .none
+ case .presentAlert:
+ state.destination = .alert(
+ AlertState {
+ TextState("Uh oh!")
+ } actions: {
+ ButtonState(role: .destructive, action: .deleteButtonTapped) {
+ TextState("Delete")
+ }
+ }
+ )
+ return .none
+ case let .presentChild(id):
+ state.destination = .child(Child.State(id: id ?? self.uuid()))
+ return .none
+ }
+ }
+ .ifLet(\.$destination, action: /Action.destination) {
+ Destinations()
+ }
+ }
+ struct Destinations: ReducerProtocol {
+ enum State: Equatable {
+ case alert(AlertState)
+ case child(Child.State)
+ }
+ enum Action: Equatable {
+ case alert(Alert)
+ case child(Child.Action)
+
+ enum Alert: Equatable {
+ case deleteButtonTapped
+ }
+ }
+ var body: some ReducerProtocol {
+ Scope(state: /State.alert, action: /Action.alert) {}
+ Scope(state: /State.child, action: /Action.child) {
+ Child()
+ }
}
}
}
- }
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- $0.uuid = .incrementing
- }
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ $0.uuid = .incrementing
+ }
- await store.send(.presentChild()) {
- $0.destination = .child(
- Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
- )
- }
- await store.send(.destination(.presented(.child(.startButtonTapped))))
- await clock.advance(by: .seconds(2))
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 1
+ await store.send(.presentChild()) {
+ $0.destination = .child(
+ Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+ )
}
- }
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 2
+ await store.send(.destination(.presented(.child(.startButtonTapped))))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 1
+ }
}
- }
- await store.send(.destination(.presented(.child(.closeButtonTapped))))
- await store.receive(.destination(.dismiss)) {
- $0.destination = nil
- }
- await store.send(.presentChild()) {
- $0.destination = .child(
- Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
- )
- }
- await clock.advance(by: .seconds(2))
- await store.send(.destination(.presented(.child(.startButtonTapped))))
- await clock.advance(by: .seconds(2))
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 1
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 2
+ }
}
- }
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 2
+ await store.send(.destination(.presented(.child(.closeButtonTapped))))
+ await store.receive(.destination(.dismiss)) {
+ $0.destination = nil
+ }
+ await store.send(.presentChild()) {
+ $0.destination = .child(
+ Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
+ )
+ }
+ await clock.advance(by: .seconds(2))
+ await store.send(.destination(.presented(.child(.startButtonTapped))))
+ await clock.advance(by: .seconds(2))
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 1
+ }
}
- }
- await store.send(
- .presentChild(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
- ) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 0
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 2
+ }
}
- }
- await clock.advance(by: .seconds(2))
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 1
+ await store.send(
+ .presentChild(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
+ ) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 0
+ }
}
- }
- await store.receive(.destination(.presented(.child(.tick)))) {
- try (/Parent.Destinations.State.child).modify(&$0.destination) {
- $0.count = 2
+ await clock.advance(by: .seconds(2))
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 1
+ }
}
- }
- await store.send(.presentAlert) {
- $0.destination = .alert(
- AlertState {
- TextState("Uh oh!")
- } actions: {
- ButtonState(role: .destructive, action: .deleteButtonTapped) {
- TextState("Delete")
- }
+ await store.receive(.destination(.presented(.child(.tick)))) {
+ try (/Parent.Destinations.State.child).modify(&$0.destination) {
+ $0.count = 2
}
- )
- }
- await store.send(.destination(.presented(.alert(.deleteButtonTapped)))) {
- $0.destination = nil
- $0.isDeleted = true
+ }
+ await store.send(.presentAlert) {
+ $0.destination = .alert(
+ AlertState {
+ TextState("Uh oh!")
+ } actions: {
+ ButtonState(role: .destructive, action: .deleteButtonTapped) {
+ TextState("Delete")
+ }
+ }
+ )
+ }
+ await store.send(.destination(.presented(.alert(.deleteButtonTapped)))) {
+ $0.destination = nil
+ $0.isDeleted = true
+ }
}
}
}
@@ -991,487 +997,492 @@ import XCTest
await childPresentationTask.cancel()
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testNavigation_cancelID_parentCancelTwoChildren() async {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case response(Int)
- case startButtonTapped
- }
- enum CancelID { case effect }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case let .response(value):
- state.count = value
- return .none
- case .startButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.response(42))
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case response(Int)
+ case startButtonTapped
+ }
+ enum CancelID { case effect }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case let .response(value):
+ state.count = value
+ return .none
+ case .startButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.response(42))
+ }
}
+ .cancellable(id: CancelID.effect)
}
- .cancellable(id: CancelID.effect)
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child1: Child.State?
- @PresentationState var child2: Child.State?
- }
- enum Action: Equatable {
- case child1(PresentationAction)
- case child2(PresentationAction)
- case stopButtonTapped
- case presentChildren
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child1, .child2:
- return .none
- case .stopButtonTapped:
- return .cancel(id: Child.CancelID.effect)
- case .presentChildren:
- state.child1 = Child.State()
- state.child2 = Child.State()
- return .none
- }
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child1: Child.State?
+ @PresentationState var child2: Child.State?
}
- .ifLet(\.$child1, action: /Action.child1) {
- Child()
+ enum Action: Equatable {
+ case child1(PresentationAction)
+ case child2(PresentationAction)
+ case stopButtonTapped
+ case presentChildren
}
- .ifLet(\.$child2, action: /Action.child2) {
- Child()
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child1, .child2:
+ return .none
+ case .stopButtonTapped:
+ return .cancel(id: Child.CancelID.effect)
+ case .presentChildren:
+ state.child1 = Child.State()
+ state.child2 = Child.State()
+ return .none
+ }
+ }
+ .ifLet(\.$child1, action: /Action.child1) {
+ Child()
+ }
+ .ifLet(\.$child2, action: /Action.child2) {
+ Child()
+ }
}
}
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.presentChildren) {
- $0.child1 = Child.State()
- $0.child2 = Child.State()
- }
- await store.send(.child1(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42)))) {
- $0.child1?.count = 42
- }
- await store.send(.child2(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42))))
- await store.receive(.child2(.presented(.response(42)))) {
- $0.child2?.count = 42
- }
- await store.send(.stopButtonTapped)
- await clock.run()
- await store.send(.child1(.dismiss)) {
- $0.child1 = nil
- }
- await store.send(.child2(.dismiss)) {
- $0.child2 = nil
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.presentChildren) {
+ $0.child1 = Child.State()
+ $0.child2 = Child.State()
+ }
+ await store.send(.child1(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42)))) {
+ $0.child1?.count = 42
+ }
+ await store.send(.child2(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42))))
+ await store.receive(.child2(.presented(.response(42)))) {
+ $0.child2?.count = 42
+ }
+ await store.send(.stopButtonTapped)
+ await clock.run()
+ await store.send(.child1(.dismiss)) {
+ $0.child1 = nil
+ }
+ await store.send(.child2(.dismiss)) {
+ $0.child2 = nil
+ }
}
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
func testNavigation_cancelID_childCannotCancelSibling() async throws {
- struct Child: ReducerProtocol {
- struct State: Equatable {
- var count = 0
- }
- enum Action: Equatable {
- case response(Int)
- case startButtonTapped
- case stopButtonTapped
- }
- enum CancelID { case effect }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case let .response(value):
- state.count = value
- return .none
- case .startButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.response(42))
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ var count = 0
+ }
+ enum Action: Equatable {
+ case response(Int)
+ case startButtonTapped
+ case stopButtonTapped
+ }
+ enum CancelID { case effect }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case let .response(value):
+ state.count = value
+ return .none
+ case .startButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.response(42))
+ }
}
+ .cancellable(id: CancelID.effect)
+ case .stopButtonTapped:
+ return .cancel(id: CancelID.effect)
}
- .cancellable(id: CancelID.effect)
- case .stopButtonTapped:
- return .cancel(id: CancelID.effect)
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child1: Child.State?
- @PresentationState var child2: Child.State?
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child1: Child.State?
+ @PresentationState var child2: Child.State?
+ }
+ enum Action: Equatable {
+ case child1(PresentationAction)
+ case child2(PresentationAction)
+ case presentChildren
+ }
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child1, .child2:
+ return .none
+ case .presentChildren:
+ state.child1 = Child.State()
+ state.child2 = Child.State()
+ return .none
+ }
+ }
+ .ifLet(\.$child1, action: /Action.child1) {
+ Child()
+ }
+ .ifLet(\.$child2, action: /Action.child2) {
+ Child()
+ }
+ }
}
- enum Action: Equatable {
- case child1(PresentationAction)
- case child2(PresentationAction)
- case presentChildren
+
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.presentChildren) {
+ $0.child1 = Child.State()
+ $0.child2 = Child.State()
+ }
+ await store.send(.child1(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42)))) {
+ $0.child1?.count = 42
+ }
+ await store.send(.child2(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42))))
+ await store.receive(.child2(.presented(.response(42)))) {
+ $0.child2?.count = 42
+ }
+
+ await store.send(.child1(.presented(.stopButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child2(.presented(.response(42))))
+
+ await store.send(.child2(.presented(.stopButtonTapped)))
+ await clock.advance(by: .seconds(1))
+
+ await clock.run()
+ await store.send(.child1(.dismiss)) {
+ $0.child1 = nil
+ }
+ await store.send(.child2(.dismiss)) {
+ $0.child2 = nil
+ }
}
- var body: some ReducerProtocol {
- Reduce { state, action in
+ }
+ }
+
+ func testNavigation_cancelID_childCannotCancelIdentifiableSibling() async throws {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable, Identifiable {
+ let id: UUID
+ var count = 0
+ }
+ enum Action: Equatable {
+ case response(Int)
+ case startButtonTapped
+ case stopButtonTapped
+ }
+ enum CancelID { case effect }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
switch action {
- case .child1, .child2:
- return .none
- case .presentChildren:
- state.child1 = Child.State()
- state.child2 = Child.State()
+ case let .response(value):
+ state.count = value
return .none
+ case .startButtonTapped:
+ return .run { send in
+ for await _ in self.clock.timer(interval: .seconds(1)) {
+ await send(.response(42))
+ }
+ }
+ .cancellable(id: CancelID.effect)
+ case .stopButtonTapped:
+ return .cancel(id: CancelID.effect)
}
}
- .ifLet(\.$child1, action: /Action.child1) {
- Child()
+ }
+
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child1: Child.State?
+ @PresentationState var child2: Child.State?
}
- .ifLet(\.$child2, action: /Action.child2) {
- Child()
+ enum Action: Equatable {
+ case child1(PresentationAction)
+ case child2(PresentationAction)
+ case presentChildren
+ }
+ @Dependency(\.uuid) var uuid
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child1, .child2:
+ return .none
+ case .presentChildren:
+ state.child1 = Child.State(id: self.uuid())
+ state.child2 = Child.State(id: self.uuid())
+ return .none
+ }
+ }
+ .ifLet(\.$child1, action: /Action.child1) {
+ Child()
+ }
+ .ifLet(\.$child2, action: /Action.child2) {
+ Child()
+ }
}
}
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.presentChildren) {
- $0.child1 = Child.State()
- $0.child2 = Child.State()
- }
- await store.send(.child1(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42)))) {
- $0.child1?.count = 42
- }
- await store.send(.child2(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42))))
- await store.receive(.child2(.presented(.response(42)))) {
- $0.child2?.count = 42
- }
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ $0.uuid = .incrementing
+ }
+ await store.send(.presentChildren) {
+ $0.child1 = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+ $0.child2 = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
+ }
+ await store.send(.child1(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42)))) {
+ $0.child1?.count = 42
+ }
+ await store.send(.child2(.presented(.startButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child1(.presented(.response(42))))
+ await store.receive(.child2(.presented(.response(42)))) {
+ $0.child2?.count = 42
+ }
- await store.send(.child1(.presented(.stopButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child2(.presented(.response(42))))
+ await store.send(.child1(.presented(.stopButtonTapped)))
+ await clock.advance(by: .seconds(1))
+ await store.receive(.child2(.presented(.response(42))))
- await store.send(.child2(.presented(.stopButtonTapped)))
- await clock.advance(by: .seconds(1))
+ await store.send(.child2(.presented(.stopButtonTapped)))
+ await clock.advance(by: .seconds(1))
- await clock.run()
- await store.send(.child1(.dismiss)) {
- $0.child1 = nil
- }
- await store.send(.child2(.dismiss)) {
- $0.child2 = nil
+ await clock.run()
+ await store.send(.child1(.dismiss)) {
+ $0.child1 = nil
+ }
+ await store.send(.child2(.dismiss)) {
+ $0.child2 = nil
+ }
}
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testNavigation_cancelID_childCannotCancelIdentifiableSibling() async throws {
- struct Child: ReducerProtocol {
- struct State: Equatable, Identifiable {
- let id: UUID
- var count = 0
- }
- enum Action: Equatable {
- case response(Int)
- case startButtonTapped
- case stopButtonTapped
- }
- enum CancelID { case effect }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case let .response(value):
- state.count = value
- return .none
- case .startButtonTapped:
- return .run { send in
- for await _ in self.clock.timer(interval: .seconds(1)) {
- await send(.response(42))
- }
+ func testNavigation_cancelID_childCannotCancelParent() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Child: ReducerProtocol {
+ struct State: Equatable {}
+ enum Action: Equatable {
+ case stopButtonTapped
+ }
+ func reduce(into state: inout State, action: Action) -> EffectTask {
+ switch action {
+ case .stopButtonTapped:
+ return .cancel(id: Parent.CancelID.effect)
}
- .cancellable(id: CancelID.effect)
- case .stopButtonTapped:
- return .cancel(id: CancelID.effect)
}
}
- }
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child1: Child.State?
- @PresentationState var child2: Child.State?
- }
- enum Action: Equatable {
- case child1(PresentationAction)
- case child2(PresentationAction)
- case presentChildren
- }
- @Dependency(\.uuid) var uuid
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child1, .child2:
- return .none
- case .presentChildren:
- state.child1 = Child.State(id: self.uuid())
- state.child2 = Child.State(id: self.uuid())
- return .none
- }
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child: Child.State?
+ var count = 0
}
- .ifLet(\.$child1, action: /Action.child1) {
- Child()
+ enum Action: Equatable {
+ case child(PresentationAction)
+ case presentChild
+ case response(Int)
+ case startButtonTapped
+ case stopButtonTapped
}
- .ifLet(\.$child2, action: /Action.child2) {
- Child()
+ enum CancelID { case effect }
+ @Dependency(\.continuousClock) var clock
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .presentChild:
+ state.child = Child.State()
+ return .none
+ case let .response(value):
+ state.count = value
+ return .none
+ case .startButtonTapped:
+ return .task {
+ try await self.clock.sleep(for: .seconds(1))
+ return .response(42)
+ }
+ .cancellable(id: CancelID.effect)
+ case .stopButtonTapped:
+ return .cancel(id: CancelID.effect)
+ }
+ }
+ .ifLet(\.$child, action: /Action.child) {
+ Child()
+ }
}
}
- }
- await _withMainSerialExecutor {
let clock = TestClock()
let store = TestStore(
initialState: Parent.State(),
reducer: Parent()
) {
$0.continuousClock = clock
- $0.uuid = .incrementing
- }
- await store.send(.presentChildren) {
- $0.child1 = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
- $0.child2 = Child.State(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!)
}
- await store.send(.child1(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42)))) {
- $0.child1?.count = 42
- }
- await store.send(.child2(.presented(.startButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child1(.presented(.response(42))))
- await store.receive(.child2(.presented(.response(42)))) {
- $0.child2?.count = 42
+ await store.send(.presentChild) {
+ $0.child = Child.State()
}
-
- await store.send(.child1(.presented(.stopButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.child2(.presented(.response(42))))
-
- await store.send(.child2(.presented(.stopButtonTapped)))
+ await store.send(.startButtonTapped)
+ await store.send(.child(.presented(.stopButtonTapped)))
await clock.advance(by: .seconds(1))
-
- await clock.run()
- await store.send(.child1(.dismiss)) {
- $0.child1 = nil
+ await store.receive(.response(42)) {
+ $0.count = 42
}
- await store.send(.child2(.dismiss)) {
- $0.child2 = nil
+ await store.send(.stopButtonTapped)
+ await store.send(.child(.dismiss)) {
+ $0.child = nil
}
}
}
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testNavigation_cancelID_childCannotCancelParent() async {
- struct Child: ReducerProtocol {
- struct State: Equatable {}
- enum Action: Equatable {
- case stopButtonTapped
- }
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .stopButtonTapped:
- return .cancel(id: Parent.CancelID.effect)
+ func testNavigation_cancelID_parentDismissGrandchild() async {
+ if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
+ struct Grandchild: ReducerProtocol {
+ struct State: Equatable {}
+ enum Action: Equatable {
+ case response(Int)
+ case startButtonTapped
}
- }
- }
-
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child: Child.State?
- var count = 0
- }
- enum Action: Equatable {
- case child(PresentationAction)
- case presentChild
- case response(Int)
- case startButtonTapped
- case stopButtonTapped
- }
- enum CancelID { case effect }
- @Dependency(\.continuousClock) var clock
- var body: some ReducerProtocol {
- Reduce { state, action in
+ enum CancelID { case effect }
+ @Dependency(\.continuousClock) var clock
+ func reduce(into state: inout State, action: Action) -> EffectTask {
switch action {
- case .child:
- return .none
- case .presentChild:
- state.child = Child.State()
- return .none
- case let .response(value):
- state.count = value
+ case .response:
return .none
case .startButtonTapped:
return .task {
- try await self.clock.sleep(for: .seconds(1))
+ try await clock.sleep(for: .seconds(0))
return .response(42)
}
.cancellable(id: CancelID.effect)
- case .stopButtonTapped:
- return .cancel(id: CancelID.effect)
}
}
- .ifLet(\.$child, action: /Action.child) {
- Child()
- }
}
- }
-
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.presentChild) {
- $0.child = Child.State()
- }
- await store.send(.startButtonTapped)
- await store.send(.child(.presented(.stopButtonTapped)))
- await clock.advance(by: .seconds(1))
- await store.receive(.response(42)) {
- $0.count = 42
- }
- await store.send(.stopButtonTapped)
- await store.send(.child(.dismiss)) {
- $0.child = nil
- }
- }
- @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
- func testNavigation_cancelID_parentDismissGrandchild() async {
- struct Grandchild: ReducerProtocol {
- struct State: Equatable {}
- enum Action: Equatable {
- case response(Int)
- case startButtonTapped
- }
- enum CancelID { case effect }
- @Dependency(\.continuousClock) var clock
- func reduce(into state: inout State, action: Action) -> EffectTask {
- switch action {
- case .response:
- return .none
- case .startButtonTapped:
- return .task {
- try await clock.sleep(for: .seconds(0))
- return .response(42)
+ struct Child: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var grandchild: Grandchild.State?
+ }
+ enum Action: Equatable {
+ case grandchild(PresentationAction)
+ case presentGrandchild
+ }
+ var body: some ReducerProtocolOf {
+ Reduce { state, action in
+ switch action {
+ case .grandchild:
+ return .none
+ case .presentGrandchild:
+ state.grandchild = Grandchild.State()
+ return .none
+ }
+ }
+ .ifLet(\.$grandchild, action: /Action.grandchild) {
+ Grandchild()
}
- .cancellable(id: CancelID.effect)
}
}
- }
- struct Child: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var grandchild: Grandchild.State?
- }
- enum Action: Equatable {
- case grandchild(PresentationAction)
- case presentGrandchild
- }
- var body: some ReducerProtocolOf {
- Reduce { state, action in
- switch action {
- case .grandchild:
- return .none
- case .presentGrandchild:
- state.grandchild = Grandchild.State()
- return .none
- }
+ struct Parent: ReducerProtocol {
+ struct State: Equatable {
+ @PresentationState var child: Child.State?
}
- .ifLet(\.$grandchild, action: /Action.grandchild) {
- Grandchild()
+ enum Action: Equatable {
+ case child(PresentationAction)
+ case dismissGrandchild
+ case presentChild
}
- }
- }
-
- struct Parent: ReducerProtocol {
- struct State: Equatable {
- @PresentationState var child: Child.State?
- }
- enum Action: Equatable {
- case child(PresentationAction)
- case dismissGrandchild
- case presentChild
- }
- var body: some ReducerProtocol {
- Reduce { state, action in
- switch action {
- case .child:
- return .none
- case .dismissGrandchild:
- return .send(.child(.presented(.grandchild(.dismiss))))
- case .presentChild:
- state.child = Child.State()
- return .none
+ var body: some ReducerProtocol {
+ Reduce { state, action in
+ switch action {
+ case .child:
+ return .none
+ case .dismissGrandchild:
+ return .send(.child(.presented(.grandchild(.dismiss))))
+ case .presentChild:
+ state.child = Child.State()
+ return .none
+ }
+ }
+ .ifLet(\.$child, action: /Action.child) {
+ Child()
}
- }
- .ifLet(\.$child, action: /Action.child) {
- Child()
}
}
- }
- await _withMainSerialExecutor {
- let clock = TestClock()
- let store = TestStore(
- initialState: Parent.State(),
- reducer: Parent()
- ) {
- $0.continuousClock = clock
- }
- await store.send(.presentChild) {
- $0.child = Child.State()
- }
- await store.send(.child(.presented(.presentGrandchild))) {
- $0.child?.grandchild = Grandchild.State()
- }
+ await _withMainSerialExecutor {
+ let clock = TestClock()
+ let store = TestStore(
+ initialState: Parent.State(),
+ reducer: Parent()
+ ) {
+ $0.continuousClock = clock
+ }
+ await store.send(.presentChild) {
+ $0.child = Child.State()
+ }
+ await store.send(.child(.presented(.presentGrandchild))) {
+ $0.child?.grandchild = Grandchild.State()
+ }
- await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped)))))
- await clock.advance()
- await store.receive(.child(.presented(.grandchild(.presented(.response(42))))))
+ await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped)))))
+ await clock.advance()
+ await store.receive(.child(.presented(.grandchild(.presented(.response(42))))))
- await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped)))))
- await store.send(.dismissGrandchild)
- await store.receive(.child(.presented(.grandchild(.dismiss)))) {
- $0.child?.grandchild = nil
- }
- await store.send(.child(.dismiss)) {
- $0.child = nil
+ await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped)))))
+ await store.send(.dismissGrandchild)
+ await store.receive(.child(.presented(.grandchild(.dismiss)))) {
+ $0.child?.grandchild = nil
+ }
+ await store.send(.child(.dismiss)) {
+ $0.child = nil
+ }
}
}
}
@@ -1722,7 +1733,8 @@ import XCTest
// NB: Ideally the "dismiss" effect would be automatically torn down by the TestStore.
XCTExpectFailure {
- $0.compactDescription.contains("""
+ $0.compactDescription.contains(
+ """
An effect returned for this action is still running. It must complete before the end of \
the test. …
""")
@@ -1800,6 +1812,9 @@ import XCTest
}
func testPresentation_DestinationEnum_IdentityChange() async {
+ // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed.
+ XCTExpectFailure()
+
struct Child: ReducerProtocol {
struct State: Equatable, Identifiable {
var id = DependencyValues._current.uuid()