diff --git a/Examples/CaseStudiesTests/NavigationPathTests.swift b/Examples/CaseStudiesTests/NavigationPathTests.swift index da07a0fe17..0fcd17aec3 100644 --- a/Examples/CaseStudiesTests/NavigationPathTests.swift +++ b/Examples/CaseStudiesTests/NavigationPathTests.swift @@ -83,8 +83,8 @@ final class NavigationPathTests: XCTestCase { XCTExpectFailure { $0.compactDescription.hasPrefix( """ - No "navigationDestination(for: String.self) { … }" was found among the view controllers on \ - the path. + failed - No "navigationDestination(for: String.self) { … }" was found among the view \ + controllers on the path. """ ) } @@ -117,8 +117,8 @@ final class NavigationPathTests: XCTestCase { XCTExpectFailure { $0.compactDescription.hasPrefix( """ - No "navigationDestination(for: String.self) { … }" was found among the view controllers on \ - the path. + failed - No "navigationDestination(for: String.self) { … }" was found among the view \ + controllers on the path. """ ) } @@ -143,8 +143,8 @@ final class NavigationPathTests: XCTestCase { XCTExpectFailure { $0.compactDescription.hasPrefix( """ - No "navigationDestination(for: String.self) { … }" was found among the view controllers on \ - the path. + failed - No "navigationDestination(for: String.self) { … }" was found among the view \ + controllers on the path. """ ) } @@ -289,8 +289,8 @@ final class NavigationPathTests: XCTestCase { XCTExpectFailure { $0.compactDescription == """ - Failed to decode item in navigation path at index 1. Perhaps the "navigationDestination" \ - declarations have changed since the path was encoded? + failed - Failed to decode item in navigation path at index 1. Perhaps the \ + "navigationDestination" declarations have changed since the path was encoded? """ } let nav = NavigationStackController(path: $path) { @@ -330,8 +330,8 @@ final class NavigationPathTests: XCTestCase { XCTExpectFailure { $0.compactDescription == """ - Failed to decode item in navigation path at index 0. Perhaps the "navigationDestination" \ - declarations have changed since the path was encoded? + failed - Failed to decode item in navigation path at index 0. Perhaps the \ + "navigationDestination" declarations have changed since the path was encoded? """ } let nav = NavigationStackController(path: $path) { diff --git a/Examples/CaseStudiesTests/PresentationTests.swift b/Examples/CaseStudiesTests/PresentationTests.swift index f5223f5869..dad2a9ca7f 100644 --- a/Examples/CaseStudiesTests/PresentationTests.swift +++ b/Examples/CaseStudiesTests/PresentationTests.swift @@ -140,12 +140,12 @@ final class PresentationTests: XCTestCase { } await assertEventuallyEqual(nav.viewControllers.count, 2) - await Task.yield() + try await Task.sleep(for: .seconds(1)) nav.popViewController(animated: false) await assertEventuallyEqual(nav.viewControllers.count, 1) await assertEventuallyNil(vc.model.pushedChild) - await Task.yield() + try await Task.sleep(for: .seconds(1)) withUITransaction(\.uiKit.disablesAnimations, true) { vc.model.pushedChild = Model() } @@ -355,11 +355,10 @@ final class PresentationTests: XCTestCase { vc.presentedViewController?.presentedViewController?.dismiss(animated: false) try await Task.sleep(for: .seconds(0.5)) - await assertEventuallyNotNil(vc.presentedViewController timeout: 2) + await assertEventuallyNotNil(vc.presentedViewController, timeout: 2) } - @MainActor - func testDismissMiddlePresentation() async throws { + @MainActor func testDismissMiddlePresentation() async throws { class VC: UIViewController { @UIBinding var isPresented = false override func loadView() { @@ -397,6 +396,10 @@ final class PresentationTests: XCTestCase { await assertEventuallyNotNil(vc.presentedViewController as? VC) await assertEventuallyNil(vc.presentedViewController?.presentedViewController, timeout: 2) } + + @MainActor func testDoublePresentation_ChangeRootBinding() async throws { + + } } @Observable @@ -432,6 +435,12 @@ private class BasicViewController: UIViewController { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { + view.backgroundColor = .init( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1), + alpha: 1 + ) super.viewDidLoad() present(isPresented: $model.isPresented) { [weak self] in self?.isPresenting = false diff --git a/Examples/CaseStudiesTests/RuntimeWarningTests.swift b/Examples/CaseStudiesTests/RuntimeWarningTests.swift index 4dfd52ae78..8d807b91ca 100644 --- a/Examples/CaseStudiesTests/RuntimeWarningTests.swift +++ b/Examples/CaseStudiesTests/RuntimeWarningTests.swift @@ -9,7 +9,7 @@ final class RuntimeWarningTests: XCTestCase { vc.traitCollection.dismiss() } issueMatcher: { $0.compactDescription == """ - A view controller requested dismissal, but couldn't be dismissed. + failed - A view controller requested dismissal, but couldn't be dismissed. 'UITraitCollection.dismiss()' must be called from an object that was presented using a \ binding, for example 'UIViewController.present(item:)', and \ @@ -22,7 +22,7 @@ final class RuntimeWarningTests: XCTestCase { func testNavigationDestination_WithoutNavigationController() async throws { XCTExpectFailure { $0.compactDescription == """ - Can't present navigation item: "navigationController" is "nil". + failed - Can't present navigation item: "navigationController" is "nil". """ } class VC: UIViewController { @@ -40,7 +40,7 @@ final class RuntimeWarningTests: XCTestCase { func testPushValue_WithoutNavigationStack() async throws { XCTExpectFailure { $0.compactDescription == """ - Tried to push a value from outside of a navigation stack. + failed - Tried to push a value from outside of a navigation stack. 'UITraitCollection.push(value:)' must be called from an object in a \ 'NavigationStackController'. @@ -59,7 +59,7 @@ final class RuntimeWarningTests: XCTestCase { func testPushValue_WithoutNavigationController() async throws { XCTExpectFailure { $0.compactDescription == """ - Tried to push a value from outside of a navigation stack. + failed - Tried to push a value from outside of a navigation stack. 'UITraitCollection.push(value:)' must be called from an object in a \ 'NavigationStackController'. @@ -78,7 +78,7 @@ final class RuntimeWarningTests: XCTestCase { func testPush_WithoutNavigationController() async throws { XCTExpectFailure { $0.compactDescription == """ - Can't push value: "navigationController" is "nil". + failed - Can't push value: "navigationController" is "nil". """ } class VC: UIViewController { @@ -94,7 +94,7 @@ final class RuntimeWarningTests: XCTestCase { func testPush_WithoutNavigationStack() async throws { XCTExpectFailure { $0.compactDescription == """ - Tried to push a value to a non-"NavigationStackController". + failed - Tried to push a value to a non-"NavigationStackController". """ } class VC: UIViewController { @@ -110,7 +110,7 @@ final class RuntimeWarningTests: XCTestCase { func testNavigationDestinationFor_WithoutNavigationController() async throws { XCTExpectFailure { $0.compactDescription == """ - Can't register navigation destination: "navigationController" is "nil". + failed - Can't register navigation destination: "navigationController" is "nil". """ } class VC: UIViewController { @@ -128,7 +128,7 @@ final class RuntimeWarningTests: XCTestCase { func testNavigationDestinationFor_WithoutNavigationStackController() async throws { XCTExpectFailure { $0.compactDescription == """ - Tried to apply a "navigationDestination" to a non-"NavigationStackController". + failed - Tried to apply a "navigationDestination" to a non-"NavigationStackController". """ } class VC: UIViewController { diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 1d7bb41248..7ec39542c3 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -23,7 +23,11 @@ CA473836272F0D860012CAC3 /* CaseStudiesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA473830272F0D860012CAC3 /* CaseStudiesApp.swift */; }; CA473837272F0D860012CAC3 /* FactClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA473831272F0D860012CAC3 /* FactClient.swift */; }; CA473838272F0D860012CAC3 /* OptionalNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA473832272F0D860012CAC3 /* OptionalNavigation.swift */; }; - CA47383B272F0DD60012CAC3 /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA47383A272F0DD60012CAC3 /* SwiftUINavigation */; }; + CA48F2FA2C49645100BE2C3C /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA48F2F92C49645100BE2C3C /* SwiftUINavigation */; }; + CA48F2FC2C49645100BE2C3C /* UIKitNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA48F2FB2C49645100BE2C3C /* UIKitNavigation */; }; + CA48F3022C49650100BE2C3C /* IdentifiedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = CA48F3012C49650100BE2C3C /* IdentifiedCollections */; }; + CA48F3052C49650F00BE2C3C /* Tagged in Frameworks */ = {isa = PBXBuildFile; productRef = CA48F3042C49650F00BE2C3C /* Tagged */; }; + CA48F3082C49651700BE2C3C /* ConcurrencyExtras in Frameworks */ = {isa = PBXBuildFile; productRef = CA48F3072C49651700BE2C3C /* ConcurrencyExtras */; }; CA49D9542C20D4DF00E6C5BB /* ErasedNavigationStackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA49D9532C20D4DF00E6C5BB /* ErasedNavigationStackController.swift */; }; CA49D9622C20EAB000E6C5BB /* PresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5EB9D22C0525FA0034B757 /* PresentationTests.swift */; }; CA49D9632C20EAB000E6C5BB /* NavigationStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5EB9D32C0525FA0034B757 /* NavigationStackTests.swift */; }; @@ -48,7 +52,6 @@ CADCA3662C1CE8BE00DE645F /* CaseStudy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CADCA3652C1CE8BE00DE645F /* CaseStudy.swift */; }; DC6A8411291F227400B3F6C9 /* SynchronizedBindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6A8410291F227400B3F6C9 /* SynchronizedBindings.swift */; }; DC86E8712C208D8D003C0EC9 /* Text+Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC86E8702C208D8A003C0EC9 /* Text+Template.swift */; }; - DCE73E0A2947D090004EE92E /* IdentifiedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = DCE73E092947D090004EE92E /* IdentifiedCollections */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,7 +66,6 @@ /* Begin PBXFileReference section */ CA26036C2C20973600822CA5 /* DetentsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetentsHelper.swift; sourceTree = ""; }; - CA4737C3272F090F0012CAC3 /* swiftui-navigation */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "swiftui-navigation"; path = ..; sourceTree = ""; }; CA4737C8272F095F0012CAC3 /* Inventory.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Inventory.app; sourceTree = BUILT_PRODUCTS_DIR; }; CA4737CE272F09600012CAC3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CA4737F5272F09D00012CAC3 /* ItemRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemRow.swift; sourceTree = ""; }; @@ -77,6 +79,7 @@ CA473831272F0D860012CAC3 /* FactClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FactClient.swift; sourceTree = ""; }; CA473832272F0D860012CAC3 /* OptionalNavigation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalNavigation.swift; sourceTree = ""; }; CA47383C272F0F0D0012CAC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + CA48F2F82C49644000BE2C3C /* swiftui-navigation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swiftui-navigation"; path = ..; sourceTree = ""; }; CA49D9532C20D4DF00E6C5BB /* ErasedNavigationStackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErasedNavigationStackController.swift; sourceTree = ""; }; CA49D9592C20EAA400E6C5BB /* CaseStudiesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CaseStudiesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CA49D9682C20EB7200E6C5BB /* CaseStudies.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CaseStudies.xctestplan; sourceTree = ""; }; @@ -115,7 +118,6 @@ buildActionMask = 2147483647; files = ( CA4737F4272F09780012CAC3 /* SwiftUINavigation in Frameworks */, - DCE73E0A2947D090004EE92E /* IdentifiedCollections in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -123,7 +125,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CA47383B272F0DD60012CAC3 /* SwiftUINavigation in Frameworks */, + CA48F3082C49651700BE2C3C /* ConcurrencyExtras in Frameworks */, + CA48F3022C49650100BE2C3C /* IdentifiedCollections in Frameworks */, + CA48F2FA2C49645100BE2C3C /* SwiftUINavigation in Frameworks */, + CA48F3052C49650F00BE2C3C /* Tagged in Frameworks */, + CA48F2FC2C49645100BE2C3C /* UIKitNavigation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -140,7 +146,7 @@ CA47378B272F08EF0012CAC3 = { isa = PBXGroup; children = ( - CA4737C3272F090F0012CAC3 /* swiftui-navigation */, + CA48F2F82C49644000BE2C3C /* swiftui-navigation */, CA473805272F0D330012CAC3 /* CaseStudies */, CA49D95A2C20EAA400E6C5BB /* CaseStudiesTests */, CA4737F2272F09780012CAC3 /* Frameworks */, @@ -286,7 +292,6 @@ name = Inventory; packageProductDependencies = ( CA4737F3272F09780012CAC3 /* SwiftUINavigation */, - DCE73E092947D090004EE92E /* IdentifiedCollections */, ); productName = Inventory; productReference = CA4737C8272F095F0012CAC3 /* Inventory.app */; @@ -306,7 +311,11 @@ ); name = CaseStudies; packageProductDependencies = ( - CA47383A272F0DD60012CAC3 /* SwiftUINavigation */, + CA48F2F92C49645100BE2C3C /* SwiftUINavigation */, + CA48F2FB2C49645100BE2C3C /* UIKitNavigation */, + CA48F3012C49650100BE2C3C /* IdentifiedCollections */, + CA48F3042C49650F00BE2C3C /* Tagged */, + CA48F3072C49651700BE2C3C /* ConcurrencyExtras */, ); productName = CaseStudies; productReference = CA473804272F0D330012CAC3 /* CaseStudies.app */; @@ -364,9 +373,10 @@ ); mainGroup = CA47378B272F08EF0012CAC3; packageReferences = ( - DCE73E032947D063004EE92E /* XCRemoteSwiftPackageReference "swift-tagged" */, - DCE73E062947D082004EE92E /* XCRemoteSwiftPackageReference "swift-identified-collections" */, CA6453982968A06E00802931 /* XCRemoteSwiftPackageReference "swift-dependencies" */, + CA48F3002C49650100BE2C3C /* XCRemoteSwiftPackageReference "swift-identified-collections" */, + CA48F3032C49650F00BE2C3C /* XCRemoteSwiftPackageReference "swift-tagged" */, + CA48F3062C49651700BE2C3C /* XCRemoteSwiftPackageReference "swift-concurrency-extras" */, ); productRefGroup = CA473795272F08EF0012CAC3 /* Products */; projectDirPath = ""; @@ -796,28 +806,36 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - CA6453982968A06E00802931 /* XCRemoteSwiftPackageReference "swift-dependencies" */ = { + CA48F3002C49650100BE2C3C /* XCRemoteSwiftPackageReference "swift-identified-collections" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "http://github.com/pointfreeco/swift-dependencies"; + repositoryURL = "https://github.com/pointfreeco/swift-identified-collections.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + minimumVersion = 1.1.0; }; }; - DCE73E032947D063004EE92E /* XCRemoteSwiftPackageReference "swift-tagged" */ = { + CA48F3032C49650F00BE2C3C /* XCRemoteSwiftPackageReference "swift-tagged" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-tagged.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.7.0; + minimumVersion = 0.10.0; }; }; - DCE73E062947D082004EE92E /* XCRemoteSwiftPackageReference "swift-identified-collections" */ = { + CA48F3062C49651700BE2C3C /* XCRemoteSwiftPackageReference "swift-concurrency-extras" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/pointfreeco/swift-identified-collections.git"; + repositoryURL = "https://github.com/pointfreeco/swift-concurrency-extras.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + minimumVersion = 1.1.0; + }; + }; + CA6453982968A06E00802931 /* XCRemoteSwiftPackageReference "swift-dependencies" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "http://github.com/pointfreeco/swift-dependencies"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.3; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -827,15 +845,29 @@ isa = XCSwiftPackageProductDependency; productName = SwiftUINavigation; }; - CA47383A272F0DD60012CAC3 /* SwiftUINavigation */ = { + CA48F2F92C49645100BE2C3C /* SwiftUINavigation */ = { isa = XCSwiftPackageProductDependency; productName = SwiftUINavigation; }; - DCE73E092947D090004EE92E /* IdentifiedCollections */ = { + CA48F2FB2C49645100BE2C3C /* UIKitNavigation */ = { + isa = XCSwiftPackageProductDependency; + productName = UIKitNavigation; + }; + CA48F3012C49650100BE2C3C /* IdentifiedCollections */ = { isa = XCSwiftPackageProductDependency; - package = DCE73E062947D082004EE92E /* XCRemoteSwiftPackageReference "swift-identified-collections" */; + package = CA48F3002C49650100BE2C3C /* XCRemoteSwiftPackageReference "swift-identified-collections" */; productName = IdentifiedCollections; }; + CA48F3042C49650F00BE2C3C /* Tagged */ = { + isa = XCSwiftPackageProductDependency; + package = CA48F3032C49650F00BE2C3C /* XCRemoteSwiftPackageReference "swift-tagged" */; + productName = Tagged; + }; + CA48F3072C49651700BE2C3C /* ConcurrencyExtras */ = { + isa = XCSwiftPackageProductDependency; + package = CA48F3062C49651700BE2C3C /* XCRemoteSwiftPackageReference "swift-concurrency-extras" */; + productName = ConcurrencyExtras; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = CA47378C272F08EF0012CAC3 /* Project object */; diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 184b5b4159..54612ba314 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,18 +1,45 @@ { "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "487a4d151e795a5e076a7e7aedcd13c2ebff6c31", + "version" : "1.0.1" + } + }, { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "b871e5ed11a23e52c2896a92ce2c829982ff8619", - "version" : "1.4.2" + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "eb64eacfed55635a771e3410f9c91de46cf5c6a0", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" } }, { "identity" : "swift-concurrency-extras", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras.git", "state" : { "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", "version" : "1.1.0" @@ -23,8 +50,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01", - "version" : "1.0.0" + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "http://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "52018827ce21e482a36e3795bea2666b3898164c", + "version" : "1.3.4" } }, { @@ -32,35 +68,62 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-docc-plugin", "state" : { - "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", + "version" : "1.3.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.git", + "state" : { + "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "revision" : "c85092304cda8cb38d2d68454b29609a8013620b", + "version" : "1.2.1" + } + }, { "identity" : "swift-perception", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "64f7f6c28c6a4d3c4b9da2ba02383e29ab48a8cf", - "version" : "1.2.2" + "revision" : "2c75ce556a6fc106721b0dadc2c7327244ad3999", + "version" : "1.3.3" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "303e5c5c36d6a558407d364878df131c3546fad8", - "version" : "510.0.2" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } }, { - "identity" : "xctest-dynamic-overlay", + "identity" : "swift-tagged", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "location" : "https://github.com/pointfreeco/swift-tagged.git", "state" : { - "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version" : "1.0.2" + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" } } ], diff --git a/Makefile b/Makefile index 83e13b1026..d4081517e9 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ test: -workspace SwiftUINavigation.xcworkspace \ -scheme SwiftUINavigation \ -destination platform="$(PLATFORM_TVOS)" - xcodebuild \ + xcodebuild test \ -workspace SwiftUINavigation.xcworkspace \ -scheme SwiftUINavigation \ -destination platform="$(PLATFORM_WATCHOS)" diff --git a/Package.resolved b/Package.resolved index 7de41ed1da..1260d90d55 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "b871e5ed11a23e52c2896a92ce2c829982ff8619", - "version" : "1.4.2" + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", - "version" : "1.3.0" + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" } }, { @@ -46,30 +46,30 @@ } }, { - "identity" : "swift-perception", + "identity" : "swift-issue-reporting", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-perception", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", "state" : { - "revision" : "d8340521e532cffdf75a64468ff9362de8bd2bb9", - "version" : "1.2.3" + "revision" : "c85092304cda8cb38d2d68454b29609a8013620b", + "version" : "1.2.1" } }, { - "identity" : "swift-syntax", + "identity" : "swift-perception", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "303e5c5c36d6a558407d364878df131c3546fad8", - "version" : "510.0.2" + "revision" : "2c75ce556a6fc106721b0dadc2c7327244ad3999", + "version" : "1.3.3" } }, { - "identity" : "xctest-dynamic-overlay", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2", - "version" : "1.1.2" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } } ], diff --git a/Package.swift b/Package.swift index 91be57e503..fd2fef105b 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,7 @@ import PackageDescription let package = Package( + // NB: Keep this for backwards compatibility. Will rename to 'swift-navigation' in 2.0. name: "swiftui-navigation", platforms: [ .iOS(.v13), @@ -31,11 +32,11 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.2.2"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.1.0"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.2.2"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.3.3"), ], targets: [ .target( @@ -52,6 +53,7 @@ let package = Package( "SwiftUINavigationCore", "UIKitNavigation", .product(name: "CasePaths", package: "swift-case-paths"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .testTarget( @@ -64,7 +66,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .target( diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index b07cf3ecc1..13eb0ad8ec 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -3,6 +3,7 @@ import PackageDescription let package = Package( + // NB: Keep this for backwards compatibility. Will rename to 'swift-navigation' in 2.0. name: "swiftui-navigation", platforms: [ .iOS(.v13), @@ -31,11 +32,11 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.2.2"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.1.0"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.2.2"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.3.3"), ], targets: [ .target( @@ -53,6 +54,7 @@ let package = Package( "SwiftUINavigationCore", "UIKitNavigation", .product(name: "CasePaths", package: "swift-case-paths"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .testTarget( @@ -65,7 +67,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .target( diff --git a/Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md b/Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md index 3f7e7ec750..854e112d8c 100644 --- a/Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md +++ b/Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md @@ -5,8 +5,8 @@ Tools for making navigation in Swift applications simpler, more precise, and mor ## Overview This library contains a suite of tools that form the foundation for building powerful state -management and navigation APIs for SwiftUI, UIKit, AppKit, and potentially on non-Apple platforms, -such as Windows, Linux, WASM, etc. +management and navigation APIs for SwiftUI, UIKit, AppKit, and on non-Apple platforms, such as +Windows, Linux, WASM, etc. diff --git a/Sources/SwiftNavigation/UIBindable.swift b/Sources/SwiftNavigation/UIBindable.swift index 112ac29806..a79c8a9cef 100644 --- a/Sources/SwiftNavigation/UIBindable.swift +++ b/Sources/SwiftNavigation/UIBindable.swift @@ -46,21 +46,24 @@ import Perception @propertyWrapper public struct UIBindable { public var wrappedValue: Value - private let file: StaticString private let fileID: StaticString + private let filePath: StaticString private let line: UInt + private let column: UInt init( objectIdentifier: ObjectIdentifier, wrappedValue: Value, - file: StaticString, fileID: StaticString, - line: UInt + filePath: StaticString, + line: UInt, + column: UInt ) { self.wrappedValue = wrappedValue - self.file = file + self.filePath = filePath self.fileID = fileID self.line = line + self.column = column } /// Creates a bindable object from a perceptible object. @@ -70,16 +73,18 @@ public struct UIBindable { @_disfavoredOverload public init( _ wrappedValue: Value, - file: StaticString = #file, fileID: StaticString = #fileID, - line: UInt = #line + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column ) where Value: AnyObject & Perceptible { self.init( objectIdentifier: ObjectIdentifier(wrappedValue), wrappedValue: wrappedValue, - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ) } @@ -90,16 +95,18 @@ public struct UIBindable { @_disfavoredOverload public init( wrappedValue: Value, - file: StaticString = #file, fileID: StaticString = #fileID, - line: UInt = #line + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column ) where Value: AnyObject & Perceptible { self.init( objectIdentifier: ObjectIdentifier(wrappedValue), wrappedValue: wrappedValue, - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ) } @@ -122,9 +129,10 @@ public struct UIBindable { root: wrappedValue, keyPath: keyPath, transaction: UITransaction(), - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ) } } @@ -138,16 +146,18 @@ public struct UIBindable { /// creating bindable objects nested within other expressions. public init( _ wrappedValue: Value, - file: StaticString = #file, fileID: StaticString = #fileID, - line: UInt = #line + filePath: StaticString = #file, + line: UInt = #line, + column: UInt = #column ) { self.init( objectIdentifier: ObjectIdentifier(wrappedValue), wrappedValue: wrappedValue, - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ) } @@ -157,16 +167,18 @@ public struct UIBindable { /// `@UIBindable` attribute, and provide an initial value. public init( wrappedValue: Value, - file: StaticString = #file, fileID: StaticString = #fileID, - line: UInt = #line + filePath: StaticString = #file, + line: UInt = #line, + column: UInt = #column ) { self.init( objectIdentifier: ObjectIdentifier(wrappedValue), wrappedValue: wrappedValue, - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ) } } diff --git a/Sources/SwiftNavigation/UIBinding.swift b/Sources/SwiftNavigation/UIBinding.swift index d4f2290f4d..bf38d208f5 100644 --- a/Sources/SwiftNavigation/UIBinding.swift +++ b/Sources/SwiftNavigation/UIBinding.swift @@ -1,4 +1,5 @@ -import SwiftUINavigationCore +import IssueReporting +import SwiftUINavigationCore /// A property wrapper type that can read and write an observable value. /// @@ -94,17 +95,19 @@ public struct UIBinding: Sendable { root: Root, keyPath: ReferenceWritableKeyPath, transaction: UITransaction, - file: StaticString, fileID: StaticString, - line: UInt + filePath: StaticString, + line: UInt, + column: UInt ) { self.init( location: _UIBindingWeakRoot( root: root, keyPath: keyPath, - file: file, fileID: fileID, - line: line + filePath: filePath, + line: line, + column: column ), transaction: transaction ) @@ -395,30 +398,32 @@ private final class _UIBindingWeakRoot: _UIBinding, @unc let objectIdentifier: ObjectIdentifier weak var root: Root? var value: Value - let file: StaticString let fileID: StaticString + let filePath: StaticString let line: UInt + let column: UInt init( root: Root, keyPath: ReferenceWritableKeyPath, - file: StaticString, fileID: StaticString, - line: UInt + filePath: StaticString, + line: UInt, + column: UInt ) { self.keyPath = keyPath self.objectIdentifier = ObjectIdentifier(root) self.root = root self.value = root[keyPath: keyPath] - self.file = file self.fileID = fileID + self.filePath = filePath self.line = line + self.column = column } var wrappedValue: Value { get { root?[keyPath: keyPath] ?? value } set { if root == nil { - // TODO: finesse. - runtimeWarn( + reportIssue( """ Binding failed to write to '@Bindable var \(Root.self)':\(fileID):\(line) because it \ is 'nil'. @@ -426,8 +431,10 @@ private final class _UIBindingWeakRoot: _UIBinding, @unc This usually happens because the bindable model is not strongly held and so is \ deallocated. """, - file: file, - line: line + fileID: fileID, + filePath: filePath, + line: line, + column: column ) } value = newValue diff --git a/Sources/SwiftUINavigation/Internal/Deprecations.swift b/Sources/SwiftUINavigation/Internal/Deprecations.swift index 7bd2da9017..0d279bbdcd 100644 --- a/Sources/SwiftUINavigation/Internal/Deprecations.swift +++ b/Sources/SwiftUINavigation/Internal/Deprecations.swift @@ -1,4 +1,5 @@ #if canImport(SwiftUI) + import IssueReporting import SwiftUI import SwiftUINavigationCore @@ -862,8 +863,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> CaseLet ) where @@ -874,7 +877,11 @@ { self.init(`enum`) { content() - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -911,8 +918,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -933,7 +942,11 @@ self.init(`enum`) { content.value.0 content.value.1 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -981,8 +994,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1008,7 +1023,11 @@ content.value.0 content.value.1 content.value.2 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1068,8 +1087,10 @@ Case4: Sendable, Content4 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1100,7 +1121,11 @@ content.value.1 content.value.2 content.value.3 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1168,8 +1193,10 @@ Case5: Sendable, Content5 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1205,7 +1232,11 @@ content.value.2 content.value.3 content.value.4 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1281,8 +1312,10 @@ Case6: Sendable, Content6 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1323,7 +1356,11 @@ content.value.3 content.value.4 content.value.5 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1407,8 +1444,10 @@ Case7: Sendable, Content7 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1454,7 +1493,11 @@ content.value.4 content.value.5 content.value.6 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1546,8 +1589,10 @@ Case8: Sendable, Content8 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1598,7 +1643,11 @@ content.value.5 content.value.6 content.value.7 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1698,8 +1747,10 @@ Case9: Sendable, Content9 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1755,25 +1806,32 @@ content.value.6 content.value.7 content.value.8 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } } public struct _ExhaustivityCheckView: View { @EnvironmentObject private var `enum`: BindingObject - let file: StaticString + let fileID: StaticString + let filePath: StaticString let line: UInt + let column: UInt public var body: some View { #if DEBUG let message = """ - Warning: Switch.body@\(self.file):\(self.line) + Warning: Switch.body@\(fileID):\(line) "Switch" did not handle "\(describeCase(self.enum.wrappedValue.wrappedValue))" - Make sure that you exhaustively provide a "CaseLet" view for each case in "\(Enum.self)", \ - provide a "Default" view at the end of the "Switch", or use an "IfCaseLet" view instead. + Make sure that you exhaustively provide a "CaseLet" view for each case in \ + "\(Enum.self)", provide a "Default" view at the end of the "Switch", or use an \ + "IfCaseLet" view instead. """ VStack(spacing: 17) { self.exclamation() @@ -1785,7 +1843,15 @@ .foregroundColor(.white) .padding() .background(Color.red.edgesIgnoringSafeArea(.all)) - .onAppear { runtimeWarn(message, file: self.file, line: self.line) } + .onAppear { + reportIssue( + message, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } #else EmptyView() #endif diff --git a/Sources/SwiftUINavigationCore/ButtonState.swift b/Sources/SwiftUINavigationCore/ButtonState.swift index 543ff6c871..13bb3052fd 100644 --- a/Sources/SwiftUINavigationCore/ButtonState.swift +++ b/Sources/SwiftUINavigationCore/ButtonState.swift @@ -1,5 +1,6 @@ #if canImport(SwiftUI) import CustomDump + import IssueReporting import SwiftUI public struct ButtonState: Identifiable { @@ -80,7 +81,7 @@ case let .animatedSend(action, _): var output = "" customDump(self.action, to: &output, indent: 4) - runtimeWarn( + reportIssue( """ An animated action was performed asynchronously: … diff --git a/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift b/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift deleted file mode 100644 index 91e6e9f27f..0000000000 --- a/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift +++ /dev/null @@ -1,85 +0,0 @@ -@_transparent -@inline(__always) -package func runtimeWarn( - _ message: @autoclosure () -> String, - category: String? = "SwiftNavigation", - file: StaticString? = nil, - line: UInt? = nil -) { - #if DEBUG - let message = message() - let category = category ?? "Runtime Warning" - if _XCTIsTesting { - if let file, let line { - XCTFail(message, file: file, line: line) - } else { - XCTFail(message) - } - } else { - #if canImport(os) - os_log( - .fault, - dso: dso.wrappedValue, - log: OSLog(subsystem: "com.apple.runtime-issues", category: category), - "%@", - message - ) - #elseif os(WASI) - - #else - fputs("\(formatter.string(from: Date())) [\(category)] \(message)\n", stderr) - #endif - } - #endif -} - -#if DEBUG - import XCTestDynamicOverlay - - #if canImport(os) - import os - import Foundation - - // NB: Xcode runtime warnings offer a much better experience than traditional assertions and - // breakpoints, but Apple provides no means of creating custom runtime warnings ourselves. - // To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead. - // - // Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc - @usableFromInline - let dso = UncheckedSendable( - { - let count = _dyld_image_count() - for i in 0..: @unchecked Sendable { - @usableFromInline - var wrappedValue: Value - init(_ value: Value) { - self.wrappedValue = value - } - } - #elseif os(WASI) - - #else - import Foundation - - @usableFromInline - let formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:MM:SS.sssZ" - return formatter - }() - #endif -#endif diff --git a/Sources/UIKitNavigation/Navigation/Dismiss.swift b/Sources/UIKitNavigation/Navigation/Dismiss.swift index b0b076bdd8..9d312ad4aa 100644 --- a/Sources/UIKitNavigation/Navigation/Dismiss.swift +++ b/Sources/UIKitNavigation/Navigation/Dismiss.swift @@ -1,6 +1,7 @@ #if canImport(UIKit) - import UIKit + import IssueReporting import SwiftUINavigationCore + import UIKit @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @MainActor @@ -9,7 +10,7 @@ public func callAsFunction() { guard let run else { - runtimeWarn( + reportIssue( """ A view controller requested dismissal, but couldn't be dismissed. diff --git a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift index b76145caf1..bcf82cac83 100644 --- a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift +++ b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift @@ -1,7 +1,8 @@ #if canImport(UIKit) - import UIKit + import IssueReporting @_spi(Internals) import SwiftNavigation import SwiftUINavigationCore + import UIKit open class NavigationStackController: UINavigationController { fileprivate var destinations: @@ -133,7 +134,7 @@ } else if case .lazy(.element) = navigationID { if !didPushNewViewController { if let elementType = navigationID.elementType { - runtimeWarn( + reportIssue( """ No "navigationDestination(for: \(String(customDumping: elementType))) { … }" \ was found among the view controllers on the path. @@ -141,7 +142,7 @@ ) invalidIndices.insert(index) } else { - // TODO: runtimeWarn? + // TODO: reportIssue? } } } @@ -227,14 +228,14 @@ .map { navigationController.destinations.keys.contains(DestinationType($0)) } ?? false if !canPushElement { - runtimeWarn( + reportIssue( """ Failed to decode item in navigation path at index \(nextIndex). Perhaps the \ "navigationDestination" declarations have changed since the path was encoded? """ ) if let elementType = nextElement.elementType { - runtimeWarn( + reportIssue( """ Missing navigation destination while decoding a "UINavigationPath". No \ "navigationDestination(for: \(String(customDumping: elementType))) { … }" was \ @@ -310,7 +311,7 @@ fileprivate func _push(value: Element) { guard let navigationController = navigationController ?? self as? UINavigationController else { - runtimeWarn( + reportIssue( """ Can't push value: "navigationController" is "nil". """ @@ -319,7 +320,7 @@ } guard let stackController = navigationController as? NavigationStackController else { - runtimeWarn( + reportIssue( """ Tried to push a value to a non-"NavigationStackController". """ @@ -336,7 +337,7 @@ guard let navigationController = navigationController ?? self as? UINavigationController else { // TODO: Should `UIViewController` be able to lazily register? - runtimeWarn( + reportIssue( """ Can't register navigation destination: "navigationController" is "nil". """ @@ -345,7 +346,7 @@ } guard let stackController = navigationController as? NavigationStackController else { - runtimeWarn( + reportIssue( """ Tried to apply a "navigationDestination" to a non-"NavigationStackController". """ @@ -364,7 +365,7 @@ let index = stackController.path.firstIndex(of: element)! guard let value = value.decode() else { - runtimeWarn( + reportIssue( """ Failed to decode item in navigation path at index \(index). Perhaps the \ "navigationDestination" declarations have changed since the path was encoded? diff --git a/Sources/UIKitNavigation/Navigation/Presentation.swift b/Sources/UIKitNavigation/Navigation/Presentation.swift index eb58b50741..926e420ccb 100644 --- a/Sources/UIKitNavigation/Navigation/Presentation.swift +++ b/Sources/UIKitNavigation/Navigation/Presentation.swift @@ -1,4 +1,5 @@ #if canImport(UIKit) + import IssueReporting @_spi(Internals) import SwiftNavigation import UIKit import UIKitNavigationShim @@ -198,7 +199,7 @@ guard let navigationController = self?.navigationController ?? self as? UINavigationController else { - runtimeWarn( + reportIssue( """ Can't present navigation item: "navigationController" is "nil". """ @@ -212,7 +213,7 @@ guard let navigationController = self?.navigationController ?? self as? UINavigationController else { - runtimeWarn( + reportIssue( """ Can't dismiss navigation item: "navigationController" is "nil". """ diff --git a/Sources/UIKitNavigation/Navigation/Push.swift b/Sources/UIKitNavigation/Navigation/Push.swift index a2a8ef42f9..20d3d886c7 100644 --- a/Sources/UIKitNavigation/Navigation/Push.swift +++ b/Sources/UIKitNavigation/Navigation/Push.swift @@ -1,4 +1,5 @@ #if canImport(UIKit) + import IssueReporting import UIKit import SwiftUINavigationCore @@ -9,7 +10,7 @@ public func callAsFunction(value: Element) { guard let run else { - runtimeWarn( + reportIssue( """ Tried to push a value from outside of a navigation stack. diff --git a/SwiftUINavigation.xcworkspace/contents.xcworkspacedata b/SwiftNavigation.xcworkspace/contents.xcworkspacedata similarity index 100% rename from SwiftUINavigation.xcworkspace/contents.xcworkspacedata rename to SwiftNavigation.xcworkspace/contents.xcworkspacedata diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftNavigation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from SwiftUINavigation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to SwiftNavigation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 72% rename from SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index 51921f726d..54612ba314 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftNavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", - "version" : "1.0.0" + "revision" : "487a4d151e795a5e076a7e7aedcd13c2ebff6c31", + "version" : "1.0.1" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "b871e5ed11a23e52c2896a92ce2c829982ff8619", - "version" : "1.4.2" + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-clocks", "state" : { - "revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33", - "version" : "1.0.2" + "revision" : "eb64eacfed55635a771e3410f9c91de46cf5c6a0", + "version" : "1.0.3" } }, { @@ -32,14 +32,14 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", - "version" : "1.1.1" + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" } }, { "identity" : "swift-concurrency-extras", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras.git", "state" : { "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", "version" : "1.1.0" @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", - "version" : "1.3.0" + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "http://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "00bc30ca03f98881329fab7f1bebef8eba472596", - "version" : "1.3.1" + "revision" : "52018827ce21e482a36e3795bea2666b3898164c", + "version" : "1.3.4" } }, { @@ -86,8 +86,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections.git", "state" : { - "revision" : "d533cd18b0b456b106694a9899f917ee595f2666", - "version" : "1.0.2" + "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "revision" : "c85092304cda8cb38d2d68454b29609a8013620b", + "version" : "1.2.1" } }, { @@ -95,17 +104,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "d8340521e532cffdf75a64468ff9362de8bd2bb9", - "version" : "1.2.3" + "revision" : "2c75ce556a6fc106721b0dadc2c7327244ad3999", + "version" : "1.3.3" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "303e5c5c36d6a558407d364878df131c3546fad8", - "version" : "510.0.2" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } }, { @@ -116,15 +125,6 @@ "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", "version" : "0.10.0" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2", - "version" : "1.1.2" - } } ], "version" : 2 diff --git a/Tests/SwiftUINavigationTests/ButtonStateTests.swift b/Tests/SwiftUINavigationTests/ButtonStateTests.swift index 857d268063..d6ecc2bf19 100644 --- a/Tests/SwiftUINavigationTests/ButtonStateTests.swift +++ b/Tests/SwiftUINavigationTests/ButtonStateTests.swift @@ -1,15 +1,23 @@ -#if canImport(SwiftUI) +#if canImport(SwiftUI) && canImport(Testing) import CustomDump import SwiftUI import SwiftUINavigation - import XCTest + import Testing - final class ButtonStateTests: XCTestCase { - @MainActor + struct ButtonStateTests { + @Test func testAsyncAnimationWarning() async { - XCTExpectFailure { - $0.compactDescription == """ - An animated action was performed asynchronously: … + let button = ButtonState(action: .send((), animation: .easeInOut)) { + TextState("Animate!") + } + + await withKnownIssue { + await button.withAction { _ in + await Task.yield() + } + } matching: { issue in + issue.description == """ + Expectation failed: An animated action was performed asynchronously: … Action: ButtonStateAction.send( @@ -17,18 +25,10 @@ animation: Animation.easeInOut ) - Asynchronous actions cannot be animated. Evaluate this action in a synchronous closure, or \ - use 'SwiftUI.withAnimation' explicitly. + Asynchronous actions cannot be animated. Evaluate this action in a synchronous closure, \ + or use 'SwiftUI.withAnimation' explicitly. """ } - - let button = ButtonState(action: .send((), animation: .easeInOut)) { - TextState("Animate!") - } - - await button.withAction { _ in - await Task.yield() - } } } #endif // canImport(SwiftUI) diff --git a/Tests/UIKitNavigationTests/Internal/AssertEventually.swift b/Tests/UIKitNavigationTests/Internal/AssertEventually.swift index fb0900436e..76358d3640 100644 --- a/Tests/UIKitNavigationTests/Internal/AssertEventually.swift +++ b/Tests/UIKitNavigationTests/Internal/AssertEventually.swift @@ -10,8 +10,8 @@ func assertEventuallyEqual( line: UInt = #line ) async { await _assertEventually( - expression1(), - expression2(), + expression1, + expression2, condition: { $0 == $1 }, assert: XCTAssertEqual, timeout: timeout, @@ -29,8 +29,8 @@ func assertEventuallyNoDifference( line: UInt = #line ) async { await _assertEventually( - expression1(), - expression2(), + expression1, + expression2, condition: { $0 == $1 }, assert: XCTAssertNoDifference, timeout: timeout, @@ -48,8 +48,8 @@ func assertEventuallyNotEqual( line: UInt = #line ) async { await _assertEventually( - expression1(), - expression2(), + expression1, + expression2, condition: { $0 != $1 }, assert: XCTAssertNotEqual, timeout: timeout, @@ -66,7 +66,7 @@ func assertEventuallyNil( line: UInt = #line ) async { await _assertEventually( - expression(), + expression, condition: { $0 == nil }, assert: XCTAssertNil, timeout: timeout, @@ -83,7 +83,7 @@ func assertEventuallyNotNil( line: UInt = #line ) async { await _assertEventually( - expression(), + expression, condition: { $0 != nil }, assert: XCTAssertNotNil, timeout: timeout, @@ -94,8 +94,8 @@ func assertEventuallyNotNil( @MainActor private func _assertEventually( - _ expression1: @autoclosure @escaping @MainActor () -> T, - _ expression2: @autoclosure @escaping @MainActor () -> T, + _ expression1: @escaping @MainActor () -> T, + _ expression2: @escaping @MainActor () -> T, condition: (T, T) -> Bool, assert: ( @autoclosure () -> T, @@ -127,7 +127,7 @@ private func _assertEventually( @MainActor private func _assertEventually( - _ expression: @autoclosure @escaping @MainActor () -> T, + _ expression: @escaping @MainActor () -> T, condition: (T) -> Bool, assert: (@autoclosure () -> T, @autoclosure () -> String, StaticString, UInt) -> Void, timeout: TimeInterval, diff --git a/Tests/UIKitNavigationTests/MemoryManagementTests.swift b/Tests/UIKitNavigationTests/MemoryManagementTests.swift index 1bf4af8f6d..8827c42f79 100644 --- a/Tests/UIKitNavigationTests/MemoryManagementTests.swift +++ b/Tests/UIKitNavigationTests/MemoryManagementTests.swift @@ -43,7 +43,11 @@ final class MemoryManagementTests: XCTestCase { @MainActor func testNavigationStackController_ObservationDoesNotRetainModel() async { - weak var weakModel: Model? + #if swift(>=5.10) + weak nonisolated(unsafe) var weakModel: Model? + #else + weak var weakModel: Model? + #endif do { @UIBindable var model = Model() weakModel = model