From 3db798e83301d238a05a825e872318ba1dcc3e86 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 27 Sep 2026 01:22:17 +0800 Subject: [PATCH 1/4] fix(observation): use subgraphOrNil for tracking --- Example/Shared/Data/ObservationExample.swift | 54 +++++++++++++------ Package.resolved | 4 +- .../Data/Observation/ObservationUtils.swift | 2 +- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/Example/Shared/Data/ObservationExample.swift b/Example/Shared/Data/ObservationExample.swift index 2536c3e38..1d447a51c 100644 --- a/Example/Shared/Data/ObservationExample.swift +++ b/Example/Shared/Data/ObservationExample.swift @@ -10,31 +10,55 @@ import Observation import SwiftUI #endif +struct ObservationExample: View { + var body: some View { + VStack { + ObservationColorExample() + Divider() + ObservationCounterExample() + } + } +} + @Observable -private class Model { +private class ColorModel { var showRed = false } -struct ObservationExample: View { - @State private var model = Model() - - private var showRed: Bool { - get { model.showRed } - nonmutating set { model.showRed = newValue } - } +struct ObservationColorExample: View { + @State private var model = ColorModel() var body: some View { VStack { - Color(platformColor: showRed ? .red : .blue) - .frame(width: showRed ? 200 : 400, height: showRed ? 200 : 400) - } - .animation(.spring, value: showRed) - .onTapGesture { - showRed.toggle() + Color(model.showRed ? .red : .blue) + Text("Toggle") + .onTapGesture { + model.showRed.toggle() + } } } } #Preview { - ObservationExample() + ObservationColorExample() +} + +@Observable +private final class CounterModel { + var count = 0 +} + +struct ObservationCounterExample: View { + @State private var model = CounterModel() + + var body: some View { + Text("Tap to increase: \(model.count)") + .onTapGesture { + model.count += 1 + } + } +} + +#Preview { + ObservationCounterExample() } diff --git a/Package.resolved b/Package.resolved index 3ec974696..52899cd99 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "545d843801b80bc5e7a59f902c84a0a786da24d0" + "revision" : "f917dcc75c52e045a6af30336b2ae63320a9a650" } }, { @@ -16,7 +16,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenAttributeGraph", "state" : { "branch" : "main", - "revision" : "b5d71c9e750ce92dbaad44adaae09a08cc689ee9" + "revision" : "9ae4ffe251328d357cf9150118d02b47a4bd451c" } }, { diff --git a/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift b/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift index fa794e561..bcf8cf5a7 100644 --- a/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift +++ b/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift @@ -140,7 +140,7 @@ private func installObservationSlow( accessList: ObservationTracking._AccessList, attribute: Attribute ) { - guard let subgraph = attribute.identifier.subgraph2 else { + guard let subgraph = attribute.identifier.subgraphOrNil else { return } let weakViewGraph = WeakUncheckedSendable(ViewGraph.current) From f3ea3f528110103570f23a2a2e1a4c8900d52096 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 27 Sep 2026 01:38:19 +0800 Subject: [PATCH 2/4] fix(example): use dynamic support frameworks --- Example/Tuist/Package.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Example/Tuist/Package.swift b/Example/Tuist/Package.swift index e25e41446..649e40568 100644 --- a/Example/Tuist/Package.swift +++ b/Example/Tuist/Package.swift @@ -93,10 +93,11 @@ var packageProductTypes: [String: ProjectDescription.Product] = [ "OpenSwiftUITestsSupport": ProjectDescription.Product.staticFramework, "OpenSwiftUISymbolDualTestsSupport": ProjectDescription.Product.staticFramework, "OpenAttributeGraphShims": ProjectDescription.Product.staticFramework, - "OpenCoreGraphicsShims": ProjectDescription.Product.staticFramework, - "OpenObservation": ProjectDescription.Product.staticFramework, - "OpenQuartzCoreShims": ProjectDescription.Product.staticFramework, - "OpenRenderBoxShims": ProjectDescription.Product.staticFramework, + "OpenCoreGraphicsShims": ProjectDescription.Product.framework, + "OpenObservation": ProjectDescription.Product.framework, + "OpenObservationCxx": ProjectDescription.Product.staticFramework, + "OpenQuartzCoreShims": ProjectDescription.Product.framework, + "OpenRenderBoxShims": ProjectDescription.Product.framework, "SymbolLocator": ProjectDescription.Product.staticFramework, ] From bcc8a6e57c382c6b23ab20c91ebda76f22150c72 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 27 Sep 2026 01:53:29 +0800 Subject: [PATCH 3/4] test: add ObservationUXTests --- .../Data/Observation/ObservationUXTests.swift | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Example/OpenSwiftUIUXTests/Data/Observation/ObservationUXTests.swift diff --git a/Example/OpenSwiftUIUXTests/Data/Observation/ObservationUXTests.swift b/Example/OpenSwiftUIUXTests/Data/Observation/ObservationUXTests.swift new file mode 100644 index 000000000..86eb080f1 --- /dev/null +++ b/Example/OpenSwiftUIUXTests/Data/Observation/ObservationUXTests.swift @@ -0,0 +1,52 @@ +// +// ObservationUXTests.swift +// OpenSwiftUIUXTests + +#if OPENSWIFTUI +import OpenObservation +import OpenSwiftUI +#else +import Observation +import SwiftUI +#endif +import Testing + +@MainActor +@Suite +struct ObservationUXTests { + @Test + func stateModelMutationUpdatesBody() async throws { + var displayedCounts: [Int] = [] + let content = CounterView { displayedCounts.append($0) } + + try await withUXTestHost(of: content) { host in + try await host.waitUntil(displayedCounts == [0]) + for count in 1...2 { + try await host.tap() + try await host.waitUntil(displayedCounts.last == count) + #expect(displayedCounts == Array(0...count)) + } + } + } +} + +@Observable +private final class CounterModel { + var count = 0 +} + +private struct CounterView: View { + @State private var model = CounterModel() + let onChange: (Int) -> Void + + var body: some View { + let count = model.count + Text("\(count)") + .onTapGesture { + model.count += 1 + } + .onChange(of: count, initial: true) { _, newValue in + onChange(newValue) + } + } +} From 553044fd6c608015e544fccd1f1084b5a8f07b64 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 27 Sep 2026 01:54:07 +0800 Subject: [PATCH 4/4] chore: add OpenSwiftUITestsSupport for UXTests --- Example/Project.swift | 1 + Example/Tuist/Package.resolved | 2 +- Example/Tuist/Package.swift | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Example/Project.swift b/Example/Project.swift index 454f7a65b..8654dc1ba 100644 --- a/Example/Project.swift +++ b/Example/Project.swift @@ -365,6 +365,7 @@ let targets: [Target] = [ dependencies: [ .target(name: "TestingHost"), .external(name: "Hammer"), + .external(name: "OpenSwiftUITestsSupport"), ] + privateFrameworkDependencies, settings: settings(base: uxTestsSettings, xcconfig: "../Configurations/OpenSwiftUIUXTests.xcconfig") ), diff --git a/Example/Tuist/Package.resolved b/Example/Tuist/Package.resolved index 3124f9b12..118b638dd 100644 --- a/Example/Tuist/Package.resolved +++ b/Example/Tuist/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e40a454d054006bfb9fba5316a61732e012af8ee4612808f1c5360eea48c825a", + "originHash" : "186d5a1d87708273879b5f957b3119ce3c0d7e5d2d55a6103e96254d7321bcb3", "pins" : [ { "identity" : "equatable", diff --git a/Example/Tuist/Package.swift b/Example/Tuist/Package.swift index 649e40568..f60976b66 100644 --- a/Example/Tuist/Package.swift +++ b/Example/Tuist/Package.swift @@ -147,6 +147,12 @@ let packageSettings = PackageSettings( defaultSettings: .essential, defaultConfiguration: "OpenSwiftUIDebug" ), + "OpenSwiftUITestsSupport": .settings( + base: ["ENABLE_TESTING_SEARCH_PATHS": "YES"], + configurations: openSwiftUIPackageConfigurations, + defaultSettings: .essential, + defaultConfiguration: "OpenSwiftUIDebug" + ), "OpenSwiftUI": .settings( base: openSwiftUITargetSettings, configurations: openSwiftUIPackageConfigurations,