From 26c59b112d80128e870f20eccd33c0ff6340e161 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Sep 2026 22:24:27 +0800 Subject: [PATCH 1/6] fix(gesture): preserve pending exclusive gesture phases --- Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift b/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift index 5c6ba93f5..da7276a29 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift @@ -167,6 +167,8 @@ private struct ExclusivePhase: Rule where First: Gesture, Second: .active(.second(value2)) case let (.failed, .possible(.some(value2))): .possible(.second(value2)) + case (.failed, .possible(.none)): + .possible(nil) case let (.failed, .ended(value2)): .ended(.second(value2)) case (.failed, _): From 3e72f407cecd334690d20e5f00c4b8840f875040 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Sep 2026 22:24:42 +0800 Subject: [PATCH 2/6] fix(gesture): preserve event time when merging view inputs --- Sources/OpenSwiftUICore/Event/Gesture/GestureInputs.swift | 2 ++ .../OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureInputs.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureInputs.swift index 3c0635eb2..11b206d9d 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/GestureInputs.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureInputs.swift @@ -72,7 +72,9 @@ public struct _GestureInputs { _ other: _ViewInputs, viewSubgraph: Subgraph ) { + let time = viewInputs.time self.viewInputs = other + self.viewInputs.time = time self.viewInputs.copyCaches() self.viewSubgraph = viewSubgraph } diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift index fb0f09022..6217e7a82 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift @@ -192,9 +192,7 @@ extension AnyGestureResponder { childSubgraph!.apply { let subgraph = (childViewSubgraph ?? childSubgraph)! var childInputs = inputs - childInputs.viewInputs = self.inputs - childInputs.copyCaches() - childInputs.viewSubgraph = subgraph + childInputs.mergeViewInputs(self.inputs, viewSubgraph: subgraph) let childOutputs = makeChild(childInputs) outputs.overrideDefaultValues(childOutputs) } From 0d85c0a24251f535aae8ccb17f2e801a8651046b Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Sep 2026 22:24:43 +0800 Subject: [PATCH 3/6] test(gesture): cover double taps in UX tests --- .../Event/Gesture/TapGestureUXTests.swift | 8 ++++---- Example/OpenSwiftUIUXTests/UXTests/UXTestHost.swift | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Example/OpenSwiftUIUXTests/Event/Gesture/TapGestureUXTests.swift b/Example/OpenSwiftUIUXTests/Event/Gesture/TapGestureUXTests.swift index 20bdbd2da..eb5303787 100644 --- a/Example/OpenSwiftUIUXTests/Event/Gesture/TapGestureUXTests.swift +++ b/Example/OpenSwiftUIUXTests/Event/Gesture/TapGestureUXTests.swift @@ -12,15 +12,15 @@ import Testing @MainActor @Suite(.serialized) struct TapGestureUXTests { - @Test - func onTapGestureIncrementsCount() async throws { + @Test(arguments: [1, 2]) + func onTapGestureIncrementsCount(tapCount: Int) async throws { var count = 0 - let content = Color.red.onTapGesture { + let content = Color.red.onTapGesture(count: tapCount) { count += 1 } try await withUXTestHost(of: content) { host in #expect(count == 0) - try await host.tap() + try await host.tap(count: tapCount) try await host.waitUntil(count > 0) #expect(count == 1) } diff --git a/Example/OpenSwiftUIUXTests/UXTests/UXTestHost.swift b/Example/OpenSwiftUIUXTests/UXTests/UXTestHost.swift index 819de9a84..44e933846 100644 --- a/Example/OpenSwiftUIUXTests/UXTests/UXTestHost.swift +++ b/Example/OpenSwiftUIUXTests/UXTests/UXTestHost.swift @@ -72,11 +72,11 @@ func withUXTestHost( struct UXTestHost { fileprivate let events: EventGenerator - func tap() async throws { + func tap(count: Int = 1) async throws { #if os(macOS) - try await events.mouseClick() + try await events.mouseClick(numberOfTimes: count) #else - try await onMainRunLoop { try events.fingerTap() } + try await onMainRunLoop { try events.fingerTap(numberOfTimes: count) } #endif } From b27177cc18f6d1923f7e5ef2dd682f371c5c6a80 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Sep 2026 22:49:20 +0800 Subject: [PATCH 4/6] fix(gesture): treat ended phases as active --- Sources/OpenSwiftUICore/Event/Gesture/GesturePhase.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GesturePhase.swift b/Sources/OpenSwiftUICore/Event/Gesture/GesturePhase.swift index decd19810..6c9bee03d 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/GesturePhase.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/GesturePhase.swift @@ -55,10 +55,10 @@ extension GesturePhase { } package var isActive: Bool { - guard case .active = self else { - return false + switch self { + case .possible, .failed: false + case .active, .ended: true } - return true } package var isTerminal: Bool { From 777e000db7bc0d8c316226eb83053fa54c252e23 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 13:13:56 +0800 Subject: [PATCH 5/6] build: update Hammer to 0.19.2 --- Example/Tuist/Package.resolved | 6 +++--- Example/Tuist/Package.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/Tuist/Package.resolved b/Example/Tuist/Package.resolved index 0c8ed5e29..3124f9b12 100644 --- a/Example/Tuist/Package.resolved +++ b/Example/Tuist/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "a6e7ee6424276cc941aa6f911f2217c55cdabbd01be5b7dd013a2297d945a0f8", + "originHash" : "e40a454d054006bfb9fba5316a61732e012af8ee4612808f1c5360eea48c825a", "pins" : [ { "identity" : "equatable", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/OpenSwiftUIProject/Hammer", "state" : { - "revision" : "e23d66765da53f44de267c75fe061f1a2cbaa2ac", - "version" : "0.19.1" + "revision" : "cdde65d489d3792052e5b386784e9f939588b1b2", + "version" : "0.19.2" } }, { diff --git a/Example/Tuist/Package.swift b/Example/Tuist/Package.swift index 26aa1f47b..e25e41446 100644 --- a/Example/Tuist/Package.swift +++ b/Example/Tuist/Package.swift @@ -35,7 +35,7 @@ var dependencies: [PackageDescription.Package.Dependency] = [ .package(url: "https://github.com/OpenSwiftUIProject/equatable.git", branch: "main"), .package(url: "https://github.com/OpenSwiftUIProject/SymbolLocator.git", from: "0.2.0"), .package(url: "https://github.com/OpenSwiftUIProject/swift-snapshot-testing.git", exact: "1.19.3"), - .package(url: "https://github.com/OpenSwiftUIProject/Hammer.git", exact: "0.19.1"), + .package(url: "https://github.com/OpenSwiftUIProject/Hammer.git", exact: "0.19.2"), ] if openCombineCondition { From ef8ee67223f3ed1259f30457bb5a67ef74fb1894 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 15:29:34 +0800 Subject: [PATCH 6/6] test: isolate observation utilities on the main actor --- .../Data/Observation/ObservationUtilsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift index f5ba96a89..ce06c7276 100644 --- a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift +++ b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift @@ -9,6 +9,7 @@ import OpenObservation @_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore +@MainActor @Suite("ObservationUtil Tests") struct ObservationUtilTests { @@ -38,7 +39,6 @@ struct ObservationUtilTests { } #if canImport(Darwin) - @MainActor @Test("_withObservation with attribute installation") func withObservationAttribute() { let model = TestModel()