From 464d95e368b9e8087f70df035bfe96c344424493 Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:10:23 -0800 Subject: [PATCH] Add previews to integration test cases (#2551) * Add previews to all integration tests. * wip; * wip * wip * debug * xcode 15 * split out examples * wip * wip * move around tests so they don't go off screen * wip * wip * ops * wip * wip --------- Co-authored-by: Stephen Celis --- .github/workflows/ci.yml | 26 ++- .../Integration.xcodeproj/project.pbxproj | 10 -- .../Integration/BasicsTestCase.swift | 7 + .../Integration/EnumTestCase.swift | 7 + .../Integration/IdentifiedListTestCase.swift | 9 ++ .../Integration/IntegrationApp.swift | 148 +++++++++--------- .../NavigationStackBindingTestCase.swift | 52 ------ .../Integration/NavigationTestCase.swift | 7 + .../Integration/OptionalTestCase.swift | 7 + .../Integration/PresentationTestCase.swift | 7 + .../Integration/SiblingTestCase.swift | 7 + .../IntegrationUITests/BasicsTests.swift | 7 + .../IntegrationUITests/EnumTests.swift | 53 +++++++ .../IdentifiedListTests.swift | 52 ++++++ .../Internal/BaseIntegrationTests.swift | 4 +- .../Legacy/BindingLocalTests.swift | 5 + .../Legacy/EscapedWithViewStoreTests.swift | 5 + .../Legacy/ForEachBindingTests.swift | 5 + .../Legacy/LegacyNavigationTests.swift | 1 + .../Legacy/LegacyPresentationTests.swift | 1 + .../Legacy/NavigationStackBindingTests.swift | 12 -- .../Legacy/SwitchStoreTests.swift | 5 + .../IntegrationUITests/NavigationTests.swift | 11 ++ .../IntegrationUITests/OptionalTests.swift | 26 +++ .../PresentationTests.swift | 42 ++++- .../IntegrationUITests/SiblingTests.swift | 22 +++ Examples/Integration/TestCases/TestCase.swift | 1 - Makefile | 18 ++- Sources/ComposableArchitecture/Store.swift | 10 +- .../SwiftUI/WithViewStore.swift | 3 + .../ComposableArchitecture/ViewStore.swift | 26 +++ 31 files changed, 420 insertions(+), 176 deletions(-) delete mode 100644 Examples/Integration/Integration/Legacy/NavigationStackBindingTestCase.swift delete mode 100644 Examples/Integration/IntegrationUITests/Legacy/NavigationStackBindingTests.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1743ff100bf4..6af6d5d0ae98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,8 @@ jobs: - release steps: - uses: actions/checkout@v4 - - name: Select Xcode 14.3 - run: sudo xcode-select -s /Applications/Xcode_14.3.app + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app - name: Run ${{ matrix.config }} tests run: make CONFIG=${{ matrix.config }} test-library @@ -35,8 +35,8 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v4 - - name: Select Xcode 14.3 - run: sudo xcode-select -s /Applications/Xcode_14.3.app + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app - name: Build for library evolution run: make build-for-library-evolution @@ -45,8 +45,8 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v4 - - name: Select Xcode 14.3 - run: sudo xcode-select -s /Applications/Xcode_14.3.app + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app - name: Run benchmark run: make benchmark @@ -55,7 +55,17 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v4 - - name: Select Xcode 14.3 - run: sudo xcode-select -s /Applications/Xcode_14.3.app + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app - name: Run tests run: make test-examples + + integration: + name: Integration + runs-on: macos-13 + steps: + - uses: actions/checkout@v4 + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app + - name: Run tests + run: make test-integration diff --git a/Examples/Integration/Integration.xcodeproj/project.pbxproj b/Examples/Integration/Integration.xcodeproj/project.pbxproj index a2359db4ff00..732d86cc56de 100644 --- a/Examples/Integration/Integration.xcodeproj/project.pbxproj +++ b/Examples/Integration/Integration.xcodeproj/project.pbxproj @@ -11,8 +11,6 @@ CA487B2C2A15185300F54A79 /* BaseIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA487B2B2A15185300F54A79 /* BaseIntegrationTests.swift */; }; CA4BA5E929E76A7F0004FF9D /* NavigationStackTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4BA5E829E76A7F0004FF9D /* NavigationStackTestCase.swift */; }; CA4BA5EB29E76F110004FF9D /* LegacyNavigationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4BA5EA29E76F110004FF9D /* LegacyNavigationTests.swift */; }; - CA595273296DF46D00B5B695 /* NavigationStackBindingTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA595272296DF46D00B5B695 /* NavigationStackBindingTestCase.swift */; }; - CA595275296DF55A00B5B695 /* NavigationStackBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA595274296DF55A00B5B695 /* NavigationStackBindingTests.swift */; }; CA595278296DF67E00B5B695 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = CA595277296DF67E00B5B695 /* ComposableArchitecture */; }; CA8B2E9A2AC576CA008272E0 /* BasicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8B2E962AC576B5008272E0 /* BasicsTests.swift */; }; CA8B2E9B2AC576CA008272E0 /* EnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8B2E942AC576B5008272E0 /* EnumTests.swift */; }; @@ -97,8 +95,6 @@ CA487B2B2A15185300F54A79 /* BaseIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseIntegrationTests.swift; sourceTree = ""; }; CA4BA5E829E76A7F0004FF9D /* NavigationStackTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackTestCase.swift; sourceTree = ""; }; CA4BA5EA29E76F110004FF9D /* LegacyNavigationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyNavigationTests.swift; sourceTree = ""; }; - CA595272296DF46D00B5B695 /* NavigationStackBindingTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackBindingTestCase.swift; sourceTree = ""; }; - CA595274296DF55A00B5B695 /* NavigationStackBindingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackBindingTests.swift; sourceTree = ""; }; CA595276296DF66B00B5B695 /* swift-composable-architecture */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swift-composable-architecture"; path = ../..; sourceTree = ""; }; CA8B2E942AC576B5008272E0 /* EnumTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumTests.swift; sourceTree = ""; }; CA8B2E952AC576B5008272E0 /* OptionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalTests.swift; sourceTree = ""; }; @@ -183,7 +179,6 @@ E9919D41296E47A400C8716B /* BindingsAnimationsTestBench.swift */, E9919D3D296E28C800C8716B /* EscapedWithViewStoreTestCase.swift */, CAA1CB1E296DEEAC000665B1 /* ForEachBindingTestCase.swift */, - CA595272296DF46D00B5B695 /* NavigationStackBindingTestCase.swift */, CA4BA5E829E76A7F0004FF9D /* NavigationStackTestCase.swift */, DC92799A2A1E59D500B2031A /* PresentationItemTestCase.swift */, CAF5802429A5651D0042FB62 /* LegacyPresentationTestCase.swift */, @@ -198,7 +193,6 @@ DCFFB8E82A15792C006AF839 /* BindingLocalTests.swift */, E9919D3F296E3EF400C8716B /* EscapedWithViewStoreTests.swift */, CAA1CB0F296DEE79000665B1 /* ForEachBindingTests.swift */, - CA595274296DF55A00B5B695 /* NavigationStackBindingTests.swift */, CA4BA5EA29E76F110004FF9D /* LegacyNavigationTests.swift */, CAF5802629A567BB0042FB62 /* LegacyPresentationTests.swift */, DC140CC629E0E8F3006DF553 /* SwitchStoreTests.swift */, @@ -452,7 +446,6 @@ CA4BA5E929E76A7F0004FF9D /* NavigationStackTestCase.swift in Sources */, E9919D42296E47A400C8716B /* BindingsAnimationsTestBench.swift in Sources */, DCFFB8E72A156488006AF839 /* BindingLocalTestCase.swift in Sources */, - CA595273296DF46D00B5B695 /* NavigationStackBindingTestCase.swift in Sources */, CAF5802529A5651D0042FB62 /* LegacyPresentationTestCase.swift in Sources */, CA8B2EB22AC5AD63008272E0 /* NavigationTestCase.swift in Sources */, E9919D3E296E28C800C8716B /* EscapedWithViewStoreTestCase.swift in Sources */, @@ -478,7 +471,6 @@ CAF5802729A567BB0042FB62 /* LegacyPresentationTests.swift in Sources */, CA487B2C2A15185300F54A79 /* BaseIntegrationTests.swift in Sources */, CA8B2EA72AC584BE008272E0 /* SiblingTests.swift in Sources */, - CA595275296DF55A00B5B695 /* NavigationStackBindingTests.swift in Sources */, CAA1CB10296DEE79000665B1 /* ForEachBindingTests.swift in Sources */, DCFFB8E92A15792C006AF839 /* BindingLocalTests.swift in Sources */, CA8B2E9A2AC576CA008272E0 /* BasicsTests.swift in Sources */, @@ -570,7 +562,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -624,7 +615,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; diff --git a/Examples/Integration/Integration/BasicsTestCase.swift b/Examples/Integration/Integration/BasicsTestCase.swift index dfa2d3d4a07a..5db574bd7974 100644 --- a/Examples/Integration/Integration/BasicsTestCase.swift +++ b/Examples/Integration/Integration/BasicsTestCase.swift @@ -46,3 +46,10 @@ struct BasicsView: View { } } } + +struct BasicsPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + BasicsView() + } +} diff --git a/Examples/Integration/Integration/EnumTestCase.swift b/Examples/Integration/Integration/EnumTestCase.swift index 22aae01d8d64..35f9f4d7777c 100644 --- a/Examples/Integration/Integration/EnumTestCase.swift +++ b/Examples/Integration/Integration/EnumTestCase.swift @@ -147,3 +147,10 @@ struct EnumTestCase_Previews: PreviewProvider { EnumView() } } + +struct EnumPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + EnumView() + } +} diff --git a/Examples/Integration/Integration/IdentifiedListTestCase.swift b/Examples/Integration/Integration/IdentifiedListTestCase.swift index a49c7cce932b..3a007a912816 100644 --- a/Examples/Integration/Integration/IdentifiedListTestCase.swift +++ b/Examples/Integration/Integration/IdentifiedListTestCase.swift @@ -89,3 +89,12 @@ struct IdentifiedListView: View { } } } + +struct IdentifiedListPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + NavigationStack { + IdentifiedListView() + } + } +} diff --git a/Examples/Integration/Integration/IntegrationApp.swift b/Examples/Integration/Integration/IntegrationApp.swift index eebf82ee5b41..236e69471236 100644 --- a/Examples/Integration/Integration/IntegrationApp.swift +++ b/Examples/Integration/Integration/IntegrationApp.swift @@ -72,99 +72,95 @@ struct IntegrationApp: App { struct ContentView: View { @State var isBindingLocalTestCasePresented = false @State var isNavigationStackTestCasePresented = false - @State var isNavigationStackBindingTestCasePresented = false @State var isNavigationTestCasePresented = false var body: some View { NavigationStack { List { - Section { - NavigationLink("Basics") { - Form { - BasicsView() - } - } - NavigationLink("Enum") { - EnumView() - } - NavigationLink("Optional") { - OptionalView() - } - NavigationLink("Identified list") { - IdentifiedListView() - } - Button("Navigation") { - self.isNavigationTestCasePresented = true - } - .sheet(isPresented: self.$isNavigationTestCasePresented) { - NavigationTestCaseView() - } - NavigationLink("Siblings") { - SiblingFeaturesView() - } - NavigationLink("Presentation") { - PresentationView() - } - } - - Section { - ForEach(TestCase.allCases) { test in - switch test { - case .escapedWithViewStore: - NavigationLink(test.rawValue) { - EscapedWithViewStoreTestCaseView() - } - - case .forEachBinding: - NavigationLink(test.rawValue) { - ForEachBindingTestCaseView() + NavigationLink("iOS 16") { + List { + Section { + NavigationLink("Basics") { + Form { + BasicsView() + } } - - case .navigationStack: - Button(test.rawValue) { - self.isNavigationStackTestCasePresented = true + NavigationLink("Enum") { + EnumView() } - .foregroundColor(.black) - .sheet(isPresented: self.$isNavigationStackTestCasePresented) { - NavigationStackTestCaseView() + NavigationLink("Optional") { + OptionalView() } - - case .navigationStackBinding: - Button(test.rawValue) { - self.isNavigationStackBindingTestCasePresented = true + NavigationLink("Identified list") { + IdentifiedListView() } - .foregroundColor(.black) - .sheet(isPresented: self.$isNavigationStackBindingTestCasePresented) { - NavigationStackBindingTestCaseView() + Button("Navigation") { + self.isNavigationTestCasePresented = true } - - case .presentation: - NavigationLink(test.rawValue) { - PresentationTestCaseView() + .sheet(isPresented: self.$isNavigationTestCasePresented) { + NavigationTestCaseView() } - - case .presentationItem: - NavigationLink(test.rawValue) { - PresentationItemTestCaseView() + NavigationLink("Siblings") { + SiblingFeaturesView() } - - case .switchStore: - NavigationLink(test.rawValue) { - SwitchStoreTestCaseView() + NavigationLink("Presentation") { + PresentationView() } + } + } + .navigationTitle(Text("iOS 16")) + } - case .bindingLocal: - Button(test.rawValue) { - self.isBindingLocalTestCasePresented = true - } - .foregroundColor(.black) - .sheet(isPresented: self.$isBindingLocalTestCasePresented) { - BindingLocalTestCaseView() + NavigationLink("Legacy") { + List { + ForEach(TestCase.allCases) { test in + switch test { + case .escapedWithViewStore: + NavigationLink(test.rawValue) { + EscapedWithViewStoreTestCaseView() + } + + case .forEachBinding: + NavigationLink(test.rawValue) { + ForEachBindingTestCaseView() + } + + case .navigationStack: + Button(test.rawValue) { + self.isNavigationStackTestCasePresented = true + } + .foregroundColor(.black) + .sheet(isPresented: self.$isNavigationStackTestCasePresented) { + NavigationStackTestCaseView() + } + + case .presentation: + NavigationLink(test.rawValue) { + PresentationTestCaseView() + } + + case .presentationItem: + NavigationLink(test.rawValue) { + PresentationItemTestCaseView() + } + + case .switchStore: + NavigationLink(test.rawValue) { + SwitchStoreTestCaseView() + } + + case .bindingLocal: + Button(test.rawValue) { + self.isBindingLocalTestCasePresented = true + } + .foregroundColor(.black) + .sheet(isPresented: self.$isBindingLocalTestCasePresented) { + BindingLocalTestCaseView() + } } } } - } header: { - Text("Legacy") + .navigationTitle(Text("Legacy")) } Section { diff --git a/Examples/Integration/Integration/Legacy/NavigationStackBindingTestCase.swift b/Examples/Integration/Integration/Legacy/NavigationStackBindingTestCase.swift deleted file mode 100644 index acfb1f421055..000000000000 --- a/Examples/Integration/Integration/Legacy/NavigationStackBindingTestCase.swift +++ /dev/null @@ -1,52 +0,0 @@ -import ComposableArchitecture -import SwiftUI - -private struct NavigationStackBindingTestCase: Reducer { - struct State: Equatable { - var path: [Destination] = [] - enum Destination: Equatable { - case child - } - } - enum Action: Equatable, Sendable { - case goToChild - case navigationPathChanged([State.Destination]) - } - - func reduce(into state: inout State, action: Action) -> Effect { - switch action { - case .goToChild: - state.path.append(.child) - return .none - case let .navigationPathChanged(path): - state.path = path - return .none - } - } -} - -struct NavigationStackBindingTestCaseView: View { - private let store = Store(initialState: NavigationStackBindingTestCase.State()) { - NavigationStackBindingTestCase() - } - - var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - NavigationStack(path: viewStore.binding(get: \.path, send: { .navigationPathChanged($0) })) { - VStack { - Text("Root") - Button("Go to child") { - viewStore.send(.goToChild) - } - } - .navigationDestination( - for: NavigationStackBindingTestCase.State.Destination.self - ) { destination in - switch destination { - case .child: Text("Child") - } - } - } - } - } -} diff --git a/Examples/Integration/Integration/NavigationTestCase.swift b/Examples/Integration/Integration/NavigationTestCase.swift index 832e33b7af02..a7cb5ecc42ae 100644 --- a/Examples/Integration/Integration/NavigationTestCase.swift +++ b/Examples/Integration/Integration/NavigationTestCase.swift @@ -42,3 +42,10 @@ struct NavigationTestCaseView: View { } } } + +struct NavigationPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + NavigationTestCaseView() + } +} diff --git a/Examples/Integration/Integration/OptionalTestCase.swift b/Examples/Integration/Integration/OptionalTestCase.swift index 90a52c7c5bd9..dad80a9470f9 100644 --- a/Examples/Integration/Integration/OptionalTestCase.swift +++ b/Examples/Integration/Integration/OptionalTestCase.swift @@ -74,3 +74,10 @@ struct OptionalView: View { } } } + +struct OptionalPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + OptionalView() + } +} diff --git a/Examples/Integration/Integration/PresentationTestCase.swift b/Examples/Integration/Integration/PresentationTestCase.swift index fbb92c64cd6a..582dd89b7c9d 100644 --- a/Examples/Integration/Integration/PresentationTestCase.swift +++ b/Examples/Integration/Integration/PresentationTestCase.swift @@ -167,3 +167,10 @@ struct PresentationView: View { } } } + +struct PresentationPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + PresentationView() + } +} diff --git a/Examples/Integration/Integration/SiblingTestCase.swift b/Examples/Integration/Integration/SiblingTestCase.swift index fd53380c439b..70c28b8ae9f3 100644 --- a/Examples/Integration/Integration/SiblingTestCase.swift +++ b/Examples/Integration/Integration/SiblingTestCase.swift @@ -75,3 +75,10 @@ struct SiblingFeaturesView: View { } } } + +struct SiblingPreviews: PreviewProvider { + static var previews: some View { + let _ = Logger.shared.isEnabled = true + SiblingFeaturesView() + } +} diff --git a/Examples/Integration/IntegrationUITests/BasicsTests.swift b/Examples/Integration/IntegrationUITests/BasicsTests.swift index 2266f48f8055..d5aaa8ae0391 100644 --- a/Examples/Integration/IntegrationUITests/BasicsTests.swift +++ b/Examples/Integration/IntegrationUITests/BasicsTests.swift @@ -6,6 +6,7 @@ import XCTest final class BasicsTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Basics"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -17,6 +18,9 @@ final class BasicsTests: BaseIntegrationTests { """ BasicsView.body StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } self.app.buttons["Decrement"].tap() @@ -24,6 +28,9 @@ final class BasicsTests: BaseIntegrationTests { """ BasicsView.body StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } diff --git a/Examples/Integration/IntegrationUITests/EnumTests.swift b/Examples/Integration/IntegrationUITests/EnumTests.swift index 4f391ca4a2b9..dc91cf80fd2e 100644 --- a/Examples/Integration/IntegrationUITests/EnumTests.swift +++ b/Examples/Integration/IntegrationUITests/EnumTests.swift @@ -6,6 +6,7 @@ import XCTest final class EnumTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Enum"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -32,6 +33,24 @@ final class EnumTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + ViewStoreOf.init + ViewStoreOf.init + WithStore.body + WithStoreOf.body + WithStoreOf.body + WithStoreOf.body + WithStoreOf.body """ } self.app.buttons["Increment"].tap() @@ -52,6 +71,9 @@ final class EnumTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } @@ -79,6 +101,12 @@ final class EnumTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + WithStore.body + WithStoreOf.body """ } } @@ -117,6 +145,25 @@ final class EnumTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + ViewStoreOf.deinit + ViewStoreOf.init + ViewStoreOf.init + WithStore.body + WithStoreOf.body + WithStoreOf.body + WithStoreOf.body + WithStoreOf.body """ } } @@ -157,6 +204,12 @@ final class EnumTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + WithStore.body + WithStoreOf.body """ } } diff --git a/Examples/Integration/IntegrationUITests/IdentifiedListTests.swift b/Examples/Integration/IntegrationUITests/IdentifiedListTests.swift index 00bbcee7b5ea..ebef9020ff47 100644 --- a/Examples/Integration/IntegrationUITests/IdentifiedListTests.swift +++ b/Examples/Integration/IntegrationUITests/IdentifiedListTests.swift @@ -6,6 +6,7 @@ import XCTest final class IdentifiedListTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Identified list"].tap() self.clearLogs() //SnapshotTesting.isRecording = true @@ -37,6 +38,23 @@ final class IdentifiedListTests: BaseIntegrationTests { StoreOf.init StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore, (UUID, BasicsView.Feature.Action)>.deinit + ViewStore, (UUID, BasicsView.Feature.Action)>.deinit + ViewStore, (UUID, BasicsView.Feature.Action)>.init + ViewStore, (UUID, BasicsView.Feature.Action)>.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.init + ViewStore.init + ViewStoreOf.init + WithStore, (UUID, BasicsView.Feature.Action)>.body + WithStore.body + WithStore.body + WithStoreOf.body """ } } @@ -86,6 +104,37 @@ final class IdentifiedListTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + ViewStore.init + ViewStore, (UUID, BasicsView.Feature.Action)>.deinit + ViewStore, (UUID, BasicsView.Feature.Action)>.deinit + ViewStore, (UUID, BasicsView.Feature.Action)>.init + ViewStore, (UUID, BasicsView.Feature.Action)>.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.deinit + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + ViewStore.init + ViewStore.init + ViewStoreOf.deinit + ViewStoreOf.deinit + ViewStoreOf.init + ViewStoreOf.init + WithStore, (UUID, BasicsView.Feature.Action)>.body + WithStore.body + WithStore.body + WithStore.body + WithStoreOf.body + WithStoreOf.body + WithStoreOf.body """ } } @@ -110,6 +159,9 @@ final class IdentifiedListTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } diff --git a/Examples/Integration/IntegrationUITests/Internal/BaseIntegrationTests.swift b/Examples/Integration/IntegrationUITests/Internal/BaseIntegrationTests.swift index 78462a77e509..1dc1fb041986 100644 --- a/Examples/Integration/IntegrationUITests/Internal/BaseIntegrationTests.swift +++ b/Examples/Integration/IntegrationUITests/Internal/BaseIntegrationTests.swift @@ -1,3 +1,4 @@ +import Accessibility import CustomDump import InlineSnapshotTesting import XCTest @@ -12,11 +13,12 @@ class BaseIntegrationTests: XCTestCase { } override func setUp() { - // SnapshotTesting.isRecording = true + //SnapshotTesting.isRecording = true // self.continueAfterFailure = false self.app = XCUIApplication() self.app.launchEnvironment["UI_TEST"] = "true" self.app.launch() + self.app.activate() self.logs = self.app.staticTexts["composable-architecture.debug.logs"] } diff --git a/Examples/Integration/IntegrationUITests/Legacy/BindingLocalTests.swift b/Examples/Integration/IntegrationUITests/Legacy/BindingLocalTests.swift index 32a317718150..89034189663f 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/BindingLocalTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/BindingLocalTests.swift @@ -3,6 +3,11 @@ import XCTest @MainActor final class BindingLocalTests: BaseIntegrationTests { + override func setUp() { + super.setUp() + self.app.buttons["Legacy"].tap() + } + func testNoBindingWarning_FullScreenCover() { app.collectionViews.buttons[TestCase.bindingLocal.rawValue].tap() diff --git a/Examples/Integration/IntegrationUITests/Legacy/EscapedWithViewStoreTests.swift b/Examples/Integration/IntegrationUITests/Legacy/EscapedWithViewStoreTests.swift index a37f8b3422ec..34a221178e7e 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/EscapedWithViewStoreTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/EscapedWithViewStoreTests.swift @@ -4,6 +4,11 @@ import XCTest @MainActor final class EscapedWithViewStoreTests: BaseIntegrationTests { + override func setUp() { + super.setUp() + self.app.buttons["Legacy"].tap() + } + func testExample() async throws { app.collectionViews.buttons[TestCase.escapedWithViewStore.rawValue].tap() diff --git a/Examples/Integration/IntegrationUITests/Legacy/ForEachBindingTests.swift b/Examples/Integration/IntegrationUITests/Legacy/ForEachBindingTests.swift index e0faa03bf3b1..eca4eb3a1697 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/ForEachBindingTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/ForEachBindingTests.swift @@ -3,6 +3,11 @@ import XCTest @MainActor final class ForEachBindingTests: BaseIntegrationTests { + override func setUp() { + super.setUp() + self.app.buttons["Legacy"].tap() + } + func testExample() async throws { app.collectionViews.buttons[TestCase.forEachBinding.rawValue].tap() app.buttons["Remove last"].tap() diff --git a/Examples/Integration/IntegrationUITests/Legacy/LegacyNavigationTests.swift b/Examples/Integration/IntegrationUITests/Legacy/LegacyNavigationTests.swift index 5afc43163f7d..2cea30c59cee 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/LegacyNavigationTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/LegacyNavigationTests.swift @@ -6,6 +6,7 @@ import XCTest final class LegacyNavigationTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["Legacy"].tap() self.app.collectionViews.buttons[TestCase.navigationStack.rawValue].tap() } diff --git a/Examples/Integration/IntegrationUITests/Legacy/LegacyPresentationTests.swift b/Examples/Integration/IntegrationUITests/Legacy/LegacyPresentationTests.swift index db1c525bf1d9..c232d77182e2 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/LegacyPresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/LegacyPresentationTests.swift @@ -6,6 +6,7 @@ import XCTest final class LegacyPresentationTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["Legacy"].tap() self.app.collectionViews.buttons[TestCase.presentation.rawValue].tap() } diff --git a/Examples/Integration/IntegrationUITests/Legacy/NavigationStackBindingTests.swift b/Examples/Integration/IntegrationUITests/Legacy/NavigationStackBindingTests.swift deleted file mode 100644 index eb9dfe6b1a95..000000000000 --- a/Examples/Integration/IntegrationUITests/Legacy/NavigationStackBindingTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import TestCases -import XCTest - -@MainActor -final class NavigationStackBindingTests: BaseIntegrationTests { - func testExample() async throws { - app.collectionViews.buttons[TestCase.navigationStackBinding.rawValue].tap() - app.buttons["Go to child"].tap() - app.buttons["Back"].tap() - XCTAssertTrue(app.buttons["Go to child"].exists) - } -} diff --git a/Examples/Integration/IntegrationUITests/Legacy/SwitchStoreTests.swift b/Examples/Integration/IntegrationUITests/Legacy/SwitchStoreTests.swift index 00a673e0130a..0913c011833c 100644 --- a/Examples/Integration/IntegrationUITests/Legacy/SwitchStoreTests.swift +++ b/Examples/Integration/IntegrationUITests/Legacy/SwitchStoreTests.swift @@ -4,6 +4,11 @@ import XCTest @MainActor final class SwitchStoreTests: BaseIntegrationTests { + override func setUp() { + super.setUp() + self.app.buttons["Legacy"].tap() + } + func testExample() async throws { self.expectRuntimeWarnings() diff --git a/Examples/Integration/IntegrationUITests/NavigationTests.swift b/Examples/Integration/IntegrationUITests/NavigationTests.swift index 4b125b3d5fd4..010f118553c2 100644 --- a/Examples/Integration/IntegrationUITests/NavigationTests.swift +++ b/Examples/Integration/IntegrationUITests/NavigationTests.swift @@ -6,6 +6,7 @@ import XCTest final class NavigationTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Navigation"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -19,6 +20,10 @@ final class NavigationTests: BaseIntegrationTests { StackStoreOf.init StoreOf.init StoreOf.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + WithStoreOf.body """ } self.app.buttons["Increment"].tap() @@ -28,6 +33,9 @@ final class NavigationTests: BaseIntegrationTests { StackStoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } @@ -58,6 +66,9 @@ final class NavigationTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } diff --git a/Examples/Integration/IntegrationUITests/OptionalTests.swift b/Examples/Integration/IntegrationUITests/OptionalTests.swift index c0af1c094ee3..533fd5d4af00 100644 --- a/Examples/Integration/IntegrationUITests/OptionalTests.swift +++ b/Examples/Integration/IntegrationUITests/OptionalTests.swift @@ -6,6 +6,7 @@ import XCTest final class OptionalTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Optional"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -25,6 +26,16 @@ final class OptionalTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + WithStore.body + WithStoreOf.body + WithStoreOf.body """ } self.app.buttons["Increment"].tap() @@ -39,6 +50,12 @@ final class OptionalTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + WithStore.body + WithStoreOf.body """ } } @@ -53,6 +70,9 @@ final class OptionalTests: BaseIntegrationTests { """ OptionalView.body StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStore.body """ } self.app.buttons["Increment"].tap() @@ -67,6 +87,12 @@ final class OptionalTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + WithStore.body + WithStoreOf.body """ } } diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index 4fff2816e1b4..ac4ea84d1dc8 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -6,6 +6,7 @@ import XCTest final class PresentationTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Presentation"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -28,6 +29,14 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.init StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + ViewStoreOf.init + WithStoreOf.body + WithStoreOf.body """ } self.app.buttons["Increment"].tap() @@ -43,6 +52,9 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } self.app.buttons["Dismiss"].firstMatch.tap() @@ -55,9 +67,6 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.deinit StoreOf.deinit - StoreOf.deinit - StoreOf.deinit - StoreOf.deinit StoreOf.init StoreOf.scope StoreOf.scope @@ -68,6 +77,8 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStoreOf.deinit + ViewStoreOf.deinit """ } } @@ -89,6 +100,14 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.init StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + ViewStoreOf.init + ViewStoreOf.init + WithStoreOf.body + WithStoreOf.body """ } self.app.buttons["Observe child count"].tap() @@ -96,6 +115,9 @@ final class PresentationTests: BaseIntegrationTests { """ PresentationView.body StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStore.body """ } self.app.buttons["Increment"].tap() @@ -112,6 +134,12 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStore.deinit + ViewStore.init + WithStore.body + WithStoreOf.body """ } XCTAssertEqual(self.app.staticTexts["Count: 1"].exists, true) @@ -126,9 +154,6 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.deinit StoreOf.deinit - StoreOf.deinit - StoreOf.deinit - StoreOf.deinit StoreOf.init StoreOf.scope StoreOf.scope @@ -139,6 +164,11 @@ final class PresentationTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + ViewStoreOf.deinit + ViewStoreOf.deinit + WithStore.body """ } } diff --git a/Examples/Integration/IntegrationUITests/SiblingTests.swift b/Examples/Integration/IntegrationUITests/SiblingTests.swift index 5b05ae091134..bdcc247cf4a6 100644 --- a/Examples/Integration/IntegrationUITests/SiblingTests.swift +++ b/Examples/Integration/IntegrationUITests/SiblingTests.swift @@ -6,6 +6,7 @@ import XCTest final class SiblingsTests: BaseIntegrationTests { override func setUp() { super.setUp() + self.app.buttons["iOS 16"].tap() self.app.buttons["Siblings"].tap() self.clearLogs() // SnapshotTesting.isRecording = true @@ -20,6 +21,9 @@ final class SiblingsTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.init + WithStoreOf.body """ } } @@ -38,6 +42,12 @@ final class SiblingsTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + WithStoreOf.body + WithStoreOf.body """ } } @@ -56,6 +66,12 @@ final class SiblingsTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + WithStoreOf.body + WithStoreOf.body """ } } @@ -74,6 +90,12 @@ final class SiblingsTests: BaseIntegrationTests { StoreOf.scope StoreOf.scope StoreOf.scope + ViewStore.deinit + ViewStore.deinit + ViewStore.init + ViewStore.init + WithStoreOf.body + WithStoreOf.body """ } } diff --git a/Examples/Integration/TestCases/TestCase.swift b/Examples/Integration/TestCases/TestCase.swift index 9caf244696ce..3161649216b9 100644 --- a/Examples/Integration/TestCases/TestCase.swift +++ b/Examples/Integration/TestCases/TestCase.swift @@ -2,7 +2,6 @@ public enum TestCase: String, CaseIterable, Identifiable, RawRepresentable { case escapedWithViewStore = "Escaped WithViewStore" case forEachBinding = "ForEach Binding" case navigationStack = "NavigationStack" - case navigationStackBinding = "NavigationStack Binding" case presentation = "Presentation APIs" case presentationItem = "Presentation Item" case switchStore = "SwitchStore/CaseLet Warning" diff --git a/Makefile b/Makefile index 9bf7fb233f78..832c43742ff2 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ CONFIG = debug -PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iPhone,iOS-16) +PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS 17,iPhone \d\+ Pro [^M]) PLATFORM_MACOS = macOS PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst -PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,TV,tvOS-16) -PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,Watch,watchOS-9) +PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS 17,TV) +PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS 10,Watch) default: test-all @@ -14,6 +14,7 @@ test-all: test-examples test-library: for platform in "$(PLATFORM_IOS)" "$(PLATFORM_MACOS)" "$(PLATFORM_MAC_CATALYST)" "$(PLATFORM_TVOS)" "$(PLATFORM_WATCHOS)"; do \ xcodebuild test \ + -skipMacroValidation \ -configuration $(CONFIG) \ -workspace .github/package.xcworkspace \ -scheme ComposableArchitecture \ @@ -41,12 +42,19 @@ test-docs: && exit 1) test-examples: - for scheme in "CaseStudies (SwiftUI)" "CaseStudies (UIKit)" Integration Search SyncUps SpeechRecognition TicTacToe Todos VoiceMemos; do \ + for scheme in "CaseStudies (SwiftUI)" "CaseStudies (UIKit)" Search SyncUps SpeechRecognition TicTacToe Todos VoiceMemos; do \ xcodebuild test \ + -skipMacroValidation \ -scheme "$$scheme" \ -destination platform="$(PLATFORM_IOS)" || exit 1; \ done +test-integration: + xcodebuild test \ + -skipMacroValidation \ + -scheme "Integration" \ + -destination platform="$(PLATFORM_IOS)" || exit 1; + benchmark: swift run --configuration release \ swift-composable-architecture-benchmark @@ -61,5 +69,5 @@ format: .PHONY: format test-all test-swift test-workspace define udid_for -$(shell xcrun simctl list --json devices available $(1) | jq -r '.devices | to_entries | map(select(.value | add)) | sort_by(.key) | .[] | select(.key | contains("$(2)")) | .value | last.udid') +$(shell xcrun simctl list devices available '$(1)' | grep '$(2)' | sort -r | head -1 | awk -F '[()]' '{ print $$(NF-3) }') endef diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 68ad5e93fcb6..46826922f32f 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -150,7 +150,7 @@ public final class Store { @ReducerBuilder reducer: () -> R, withDependencies prepareDependencies: ((inout DependencyValues) -> Void)? = nil ) where R.State == State, R.Action == Action { - defer { Logger.shared.log("\(typeName(of: self)).init") } + defer { Logger.shared.log("\(storeTypeName(of: self)).init") } if let prepareDependencies = prepareDependencies { let (initialState, reducer) = withDependencies(prepareDependencies) { (initialState(), reducer()) @@ -170,7 +170,7 @@ public final class Store { } deinit { - Logger.shared.log("\(typeName(of: self)).deinit") + Logger.shared.log("\(storeTypeName(of: self)).deinit") } /// Calls the given closure with the current state of the store. @@ -803,7 +803,7 @@ extension ScopedReducer: AnyScopedReducer { return } childStore.stateSubject.value = newValue - Logger.shared.log("\(typeName(of: store)).scope") + Logger.shared.log("\(storeTypeName(of: store)).scope") } return childStore } @@ -887,7 +887,7 @@ public struct StoreTask: Hashable, Sendable { } } -private func typeName(of store: Store) -> String { +func storeTypeName(of store: Store) -> String { let stateType = typeName(State.self, genericsAbbreviated: false) let actionType = typeName(Action.self, genericsAbbreviated: false) // TODO: `PresentationStoreOf`, `StackStoreOf`, `IdentifiedStoreOf`? @@ -918,7 +918,7 @@ private func typeName(of store: Store) -> String { } // NB: From swift-custom-dump. Consider publicizing interface in some way to keep things in sync. -private func typeName( +func typeName( _ type: Any.Type, qualified: Bool = true, genericsAbbreviated: Bool = true diff --git a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift index 374682629e1d..fa0aabd4a472 100644 --- a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift @@ -116,6 +116,7 @@ public struct WithViewStore: View { private let line: UInt private var prefix: String? private var previousState: (ViewState) -> ViewState? + private var storeTypeName: String #endif @ObservedObject private var viewStore: ViewStore @@ -135,6 +136,7 @@ public struct WithViewStore: View { defer { previousState = currentState } return previousState } + self.storeTypeName = ComposableArchitecture.storeTypeName(of: store) #endif self.viewStore = ViewStore(store, observe: { $0 }, removeDuplicates: isDuplicate) } @@ -164,6 +166,7 @@ public struct WithViewStore: View { public var body: Content { #if DEBUG + Logger.shared.log("With\(storeTypeName).body") if let prefix = self.prefix { var stateDump = "" customDump(self.viewStore.state, to: &stateDump, indent: 2) diff --git a/Sources/ComposableArchitecture/ViewStore.swift b/Sources/ComposableArchitecture/ViewStore.swift index 37cb2e98d024..fb893b62865a 100644 --- a/Sources/ComposableArchitecture/ViewStore.swift +++ b/Sources/ComposableArchitecture/ViewStore.swift @@ -71,6 +71,9 @@ public final class ViewStore: ObservableObject { private let _send: (ViewAction) -> Task? fileprivate let _state: CurrentValueRelay private var viewCancellable: AnyCancellable? + #if DEBUG + private var storeTypeName: String + #endif /// Initializes a view store from a store which observes changes to state. /// @@ -94,6 +97,10 @@ public final class ViewStore: ObservableObject { self._send = { store.send($0, originatingFrom: nil) } self._state = CurrentValueRelay(toViewState(store.stateSubject.value)) self._isInvalidated = store._isInvalidated + #if DEBUG + self.storeTypeName = ComposableArchitecture.storeTypeName(of: store) + Logger.shared.log("View\(self.storeTypeName).init") + #endif self.viewCancellable = store.stateSubject .map(toViewState) .removeDuplicates(by: isDuplicate) @@ -104,6 +111,12 @@ public final class ViewStore: ObservableObject { } } + #if DEBUG + deinit { + Logger.shared.log("View\(self.storeTypeName).deinit") + } + #endif + /// Initializes a view store from a store which observes changes to state. /// /// It is recommended that the `observe` argument transform the store's state into the bare @@ -128,6 +141,10 @@ public final class ViewStore: ObservableObject { self._send = { store.send(fromViewAction($0), originatingFrom: nil) } self._state = CurrentValueRelay(toViewState(store.stateSubject.value)) self._isInvalidated = store._isInvalidated + #if DEBUG + self.storeTypeName = ComposableArchitecture.storeTypeName(of: store) + Logger.shared.log("View\(self.storeTypeName).init") + #endif self.viewCancellable = store.stateSubject .map(toViewState) .removeDuplicates(by: isDuplicate) @@ -139,6 +156,15 @@ public final class ViewStore: ObservableObject { } init(_ viewStore: ViewStore) { + #if DEBUG + self.storeTypeName = """ + Store<\ + \(typeName(ViewState.self, genericsAbbreviated: false)), \ + \(typeName(ViewAction.self, genericsAbbreviated: false))\ + > + """ + Logger.shared.log("View\(self.storeTypeName).init") + #endif self._send = viewStore._send self._state = viewStore._state self._isInvalidated = viewStore._isInvalidated