Skip to content

Allow an alert to present another alert #3309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Examples/Integration/Integration.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
CAF5802729A567BB0042FB62 /* LegacyPresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF5802629A567BB0042FB62 /* LegacyPresentationTests.swift */; };
DC140CC529E0BB2C006DF553 /* SwitchStoreTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC140CC429E0BB2C006DF553 /* SwitchStoreTestCase.swift */; };
DC140CC729E0E8F3006DF553 /* SwitchStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC140CC629E0E8F3006DF553 /* SwitchStoreTests.swift */; };
DC44CFC12C751C1E009F9FE4 /* MultipleAlertsTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */; };
DC6268502AD1C85E00F2E2EF /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = DC62684F2AD1C85E00F2E2EF /* InlineSnapshotTesting */; };
DC6268532AD1E06300F2E2EF /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = DC6268522AD1E06300F2E2EF /* InlineSnapshotTesting */; };
DC6E2D942AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6E2D8D2AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift */; };
Expand All @@ -73,6 +74,7 @@
DC6E2DAB2AD5C677005ACC26 /* ObservablePresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6E2DA42AD5C677005ACC26 /* ObservablePresentationTests.swift */; };
DC808D6529E91FAA0072B4A9 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC808D6429E91FAA0072B4A9 /* ComposableArchitecture */; };
DC92799B2A1E59D500B2031A /* PresentationItemTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC92799A2A1E59D500B2031A /* PresentationItemTestCase.swift */; };
DCA6716B2C7CEC550086F359 /* MultipleAlertsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */; };
DCFFB8E72A156488006AF839 /* BindingLocalTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFFB8E62A156488006AF839 /* BindingLocalTestCase.swift */; };
DCFFB8E92A15792C006AF839 /* BindingLocalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFFB8E82A15792C006AF839 /* BindingLocalTests.swift */; };
E9919D3E296E28C800C8716B /* EscapedWithViewStoreTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9919D3D296E28C800C8716B /* EscapedWithViewStoreTestCase.swift */; };
Expand Down Expand Up @@ -168,6 +170,7 @@
CAF5802629A567BB0042FB62 /* LegacyPresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentationTests.swift; sourceTree = "<group>"; };
DC140CC429E0BB2C006DF553 /* SwitchStoreTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchStoreTestCase.swift; sourceTree = "<group>"; };
DC140CC629E0E8F3006DF553 /* SwitchStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchStoreTests.swift; sourceTree = "<group>"; };
DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAlertsTestCase.swift; sourceTree = "<group>"; };
DC6E2D8D2AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableIdentifiedListTestCase.swift; sourceTree = "<group>"; };
DC6E2D8E2AD5C56F005ACC26 /* ObservableBasicsTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableBasicsTestCase.swift; sourceTree = "<group>"; };
DC6E2D8F2AD5C56F005ACC26 /* ObservableNavigationTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableNavigationTestCase.swift; sourceTree = "<group>"; };
Expand All @@ -183,6 +186,7 @@
DC6E2DA32AD5C677005ACC26 /* ObservableBasicsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableBasicsTests.swift; sourceTree = "<group>"; };
DC6E2DA42AD5C677005ACC26 /* ObservablePresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservablePresentationTests.swift; sourceTree = "<group>"; };
DC92799A2A1E59D500B2031A /* PresentationItemTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationItemTestCase.swift; sourceTree = "<group>"; };
DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAlertsTests.swift; sourceTree = "<group>"; };
DCFFB8E62A156488006AF839 /* BindingLocalTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingLocalTestCase.swift; sourceTree = "<group>"; };
DCFFB8E82A15792C006AF839 /* BindingLocalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingLocalTests.swift; sourceTree = "<group>"; };
E9919D3D296E28C800C8716B /* EscapedWithViewStoreTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EscapedWithViewStoreTestCase.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -293,6 +297,7 @@
DC6E2D8C2AD5C525005ACC26 /* iOS 17 */,
CA8B2E932AC57518008272E0 /* Legacy */,
CAA1CAFA296DEE79000665B1 /* Preview Content */,
DC44CFC02C751BEA009F9FE4 /* Test Cases */,
);
path = Integration;
sourceTree = "<group>";
Expand All @@ -313,6 +318,7 @@
CAA6BEAC2ADADE4300FF83BC /* iOS 16+17 */,
DC6E2D9D2AD5C64C005ACC26 /* iOS 17 */,
CA8B2E9D2AC576CE008272E0 /* Legacy */,
DCA671692C7CEC380086F359 /* Test Cases */,
);
path = IntegrationUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -356,6 +362,14 @@
path = TestCases;
sourceTree = "<group>";
};
DC44CFC02C751BEA009F9FE4 /* Test Cases */ = {
isa = PBXGroup;
children = (
DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */,
);
path = "Test Cases";
sourceTree = "<group>";
};
DC6E2D8B2AD5C512005ACC26 /* iOS 16 */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -416,6 +430,14 @@
path = "iOS 17";
sourceTree = "<group>";
};
DCA671692C7CEC380086F359 /* Test Cases */ = {
isa = PBXGroup;
children = (
DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */,
);
path = "Test Cases";
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -582,6 +604,7 @@
CA4BA5E929E76A7F0004FF9D /* NavigationStackTestCase.swift in Sources */,
E9919D42296E47A400C8716B /* BindingsAnimationsTestBench.swift in Sources */,
CAE2E9232B23417000EE370B /* IfLetStoreTestCase.swift in Sources */,
DC44CFC12C751C1E009F9FE4 /* MultipleAlertsTestCase.swift in Sources */,
DCFFB8E72A156488006AF839 /* BindingLocalTestCase.swift in Sources */,
DC6E2D992AD5C56F005ACC26 /* ObservableOptionalTestCase.swift in Sources */,
CA7BDDA12ADB543400277984 /* NewContainsOldTestCase.swift in Sources */,
Expand Down Expand Up @@ -623,6 +646,7 @@
CA8B2E9B2AC576CA008272E0 /* EnumTests.swift in Sources */,
DC6E2DA62AD5C677005ACC26 /* ObservableIdentifiedListTests.swift in Sources */,
CAF5802729A567BB0042FB62 /* LegacyPresentationTests.swift in Sources */,
DCA6716B2C7CEC550086F359 /* MultipleAlertsTests.swift in Sources */,
CA487B2C2A15185300F54A79 /* BaseIntegrationTests.swift in Sources */,
CA8B2EA72AC584BE008272E0 /* SiblingTests.swift in Sources */,
DC6E2DA72AD5C677005ACC26 /* ObservableNavigationTests.swift in Sources */,
Expand Down
20 changes: 18 additions & 2 deletions Examples/Integration/Integration/IntegrationApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ final class IntegrationAppDelegate: NSObject, UIApplicationDelegate {
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
UIView.setAnimationsEnabled(false)
if ProcessInfo.processInfo.environment["UI_TEST"] != nil {
UIView.setAnimationsEnabled(false)
}
Logger.shared.isEnabled = true
IssueReporters.current.append(NotificationReporter())
let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
Expand Down Expand Up @@ -185,9 +187,23 @@ struct ContentView: View {
.navigationTitle(Text("iOS 16"))
}

NavigationLink("Test cases") {
List {
ForEach(TestCase.Cases.allCases) { test in
switch test {
case .multipleAlerts:
NavigationLink(test.rawValue) {
MultipleAlertsTestCaseView()
}
}
}
}
.navigationTitle(Text("Test cases"))
}

NavigationLink("Legacy") {
List {
ForEach(TestCase.allCases) { test in
ForEach(TestCase.Legacy.allCases) { test in
switch test {
case .escapedWithViewStore:
NavigationLink(test.rawValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import ComposableArchitecture
import SwiftUI

@Reducer
private struct MultipleAlertsTestCase {
@ObservableState
struct State: Equatable {
@Presents var alert: AlertState<Action.Alert>?
}
enum Action {
case alert(PresentationAction<Alert>)
case showAlertButtonTapped

@CasePathable
enum Alert {
case anotherButtonTapped
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .alert(.presented(.anotherButtonTapped)):
if let title = state.alert?.title {
state.alert = AlertState {
title + TextState("!")
} actions: {
ButtonState(action: .anotherButtonTapped) {
TextState("Another!")
}
ButtonState(role: .cancel) {
TextState("I'm done")
}
}
}
return .none

case .alert:
return .none

case .showAlertButtonTapped:
state.alert = AlertState {
TextState("Hello")
} actions: {
ButtonState(action: .anotherButtonTapped) {
TextState("Another!")
}
ButtonState(role: .cancel) {
TextState("I'm done")
}
}
return .none
}
}
.ifLet(\.$alert, action: \.alert)
._printChanges()
}
}

struct MultipleAlertsTestCaseView: View {
@Perception.Bindable private var store = Store(initialState: MultipleAlertsTestCase.State()) {
MultipleAlertsTestCase()
}

var body: some View {
WithPerceptionTracking {
VStack {
Button("Show alert") {
store.send(.showAlertButtonTapped)
}
}
.alert($store.scope(state: \.alert, action: \.alert))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class BindingLocalTests: BaseIntegrationTests {
try XCTSkipIf(ProcessInfo.processInfo.environment["CI"] != nil)
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
app.collectionViews.buttons[TestCase.bindingLocal.rawValue].tap()
app.collectionViews.buttons[TestCase.Legacy.bindingLocal.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class EscapedWithViewStoreTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
app.collectionViews.buttons[TestCase.escapedWithViewStore.rawValue].tap()
app.collectionViews.buttons[TestCase.Legacy.escapedWithViewStore.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ final class ForEachBindingTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
app.collectionViews.buttons[TestCase.forEachBinding.rawValue].tap()
app.collectionViews.buttons[TestCase.Legacy.forEachBinding.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ final class IfLetStoreTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
self.app.buttons[TestCase.ifLetStore.rawValue].tap()
self.app.buttons[TestCase.Legacy.ifLetStore.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class LegacyNavigationTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
self.app.buttons[TestCase.navigationStack.rawValue].tap()
self.app.buttons[TestCase.Legacy.navigationStack.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class LegacyPresentationTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
self.app.buttons["Legacy"].tap()
self.app.buttons[TestCase.presentation.rawValue].tap()
self.app.buttons[TestCase.Legacy.presentation.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ final class SwitchStoreTests: BaseIntegrationTests {
override func setUpWithError() throws {
try super.setUpWithError()
app.buttons["Legacy"].tap()
app.collectionViews.buttons[TestCase.switchStore.rawValue].tap()
app.collectionViews.buttons[TestCase.Legacy.switchStore.rawValue].tap()
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import TestCases
import XCTest

final class MultipleAlertsTests: BaseIntegrationTests {
@MainActor
override func setUpWithError() throws {
try XCTSkipIf(ProcessInfo.processInfo.environment["CI"] != nil)
try super.setUpWithError()
self.app.buttons["Test cases"].tap()
app.collectionViews.buttons[TestCase.Cases.multipleAlerts.rawValue].tap()
}

@MainActor
func testMultipleAlerts() {
app.buttons["Show alert"].tap()

app.buttons["Another!"].tap()

app.buttons["I'm done"].tap()
}
}
33 changes: 22 additions & 11 deletions Examples/Integration/TestCases/TestCase.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
public enum TestCase: String, CaseIterable, Identifiable, RawRepresentable {
case escapedWithViewStore = "Escaped WithViewStore"
case ifLetStore = "IfLetStore"
case forEachBinding = "ForEach Binding"
case navigationStack = "NavigationStack"
case presentation = "Presentation APIs"
case presentationItem = "Presentation Item"
case switchStore = "SwitchStore/CaseLet Warning"
case bindingLocal = "BindingLocal Warning"

public var id: Self { self }
public enum TestCase {
case cases(Cases)
case legacy(Legacy)

public enum Cases: String, CaseIterable, Identifiable, RawRepresentable {
case multipleAlerts = "Multiple alerts"

public var id: Self { self }
}

public enum Legacy: String, CaseIterable, Identifiable, RawRepresentable {
case escapedWithViewStore = "Escaped WithViewStore"
case ifLetStore = "IfLetStore"
case forEachBinding = "ForEach Binding"
case navigationStack = "NavigationStack"
case presentation = "Presentation APIs"
case presentationItem = "Presentation Item"
case switchStore = "SwitchStore/CaseLet Warning"
case bindingLocal = "BindingLocal Warning"

public var id: Self { self }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
) -> Binding<Store<ChildState, ChildAction>?>
where Value == Store<State, Action> {
self[
id: wrappedValue.currentState[keyPath: state].flatMap(_identifiableID),
state: state,
action: action,
isInViewBody: _isInPerceptionTracking,
Expand Down Expand Up @@ -234,6 +235,7 @@
) -> Binding<Store<ChildState, ChildAction>?>
where Value == Store<State, Action> {
self[
id: wrappedValue.currentState[keyPath: state].flatMap(_identifiableID),
state: state,
action: action,
isInViewBody: _isInPerceptionTracking,
Expand Down Expand Up @@ -306,6 +308,7 @@
) -> Binding<Store<ChildState, ChildAction>?>
where Value == Store<State, Action> {
self[
id: wrappedValue.currentState[keyPath: state].flatMap(_identifiableID),
state: state,
action: action,
isInViewBody: _isInPerceptionTracking,
Expand All @@ -328,6 +331,7 @@
) -> UIBinding<Store<ChildState, ChildAction>?>
where Value == Store<State, Action> {
self[
id: wrappedValue.currentState[keyPath: state].flatMap(_identifiableID),
state: state,
action: action,
isInViewBody: _isInPerceptionTracking,
Expand All @@ -342,6 +346,7 @@
extension Store where State: ObservableState {
@_spi(Internals)
public subscript<ChildState, ChildAction>(
id id: AnyHashable?,
state state: KeyPath<State, ChildState?>,
action action: CaseKeyPath<Action, PresentationAction<ChildAction>>,
isInViewBody isInViewBody: Bool,
Expand Down Expand Up @@ -375,7 +380,8 @@
}
set {
if newValue == nil,
self.state[keyPath: state] != nil,
let childState = self.state[keyPath: state],
id == _identifiableID(childState),
!self._isInvalidated()
{
self.send(action(.dismiss))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@

XCTExpectFailure {
store[
id: nil,
state: \.destination,
action: \.destination,
isInViewBody: false,
Expand Down