diff --git a/.gitignore b/.gitignore index 54956470..4cb80c40 100755 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .DS_Store ## Xcode workspace -*.xcworkspace +#*.xcworkspace ## Build generated build/ diff --git a/.watchOS/watchOS-App/App-1-Info.plist b/.watchOS/watchOS-App/App-1-Info.plist new file mode 100644 index 00000000..1ec145d8 --- /dev/null +++ b/.watchOS/watchOS-App/App-1-Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + WKWatchKitApp + + + diff --git a/.watchOS/watchOS-App/Base.lproj/Interface.storyboard b/.watchOS/watchOS-App/Base.lproj/Interface.storyboard new file mode 100644 index 00000000..e7869ede --- /dev/null +++ b/.watchOS/watchOS-App/Base.lproj/Interface.storyboard @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/.watchOS/watchOS-Ext/Ext-1-Info.plist b/.watchOS/watchOS-Ext/Ext-1-Info.plist new file mode 100644 index 00000000..d19652ef --- /dev/null +++ b/.watchOS/watchOS-Ext/Ext-1-Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + com.viewinspector.watchOS1.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + WKWatchOnly + + + diff --git a/.watchOS/watchOS-Ext/Ext-2-Info.plist b/.watchOS/watchOS-Ext/Ext-2-Info.plist new file mode 100644 index 00000000..e253fffe --- /dev/null +++ b/.watchOS/watchOS-Ext/Ext-2-Info.plist @@ -0,0 +1,16 @@ + + + + + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + com.viewinspector.watchOS2.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + + diff --git a/.watchOS/watchOS-Ext/watchOSApp+Testable.swift b/.watchOS/watchOS-Ext/watchOSApp+Testable.swift new file mode 100644 index 00000000..0fe66c45 --- /dev/null +++ b/.watchOS/watchOS-Ext/watchOSApp+Testable.swift @@ -0,0 +1,42 @@ +import SwiftUI +import WatchKit +import Combine + +#if !(os(watchOS) && DEBUG) + +typealias RootView = T +typealias TestViewSubject = Set + +extension View { + @inline(__always) + func testable(_ injector: TestViewSubject) -> Self { + self + } +} + +#else + +typealias RootView = ModifiedContent +typealias TestViewSubject = CurrentValueSubject<[(String, AnyView)], Never> + +extension View { + func testable(_ injector: TestViewSubject) -> ModifiedContent { + modifier(TestViewHost(injector: injector)) + } +} + +struct TestViewHost: ViewModifier { + + @State private var hostedViews: [(String, AnyView)] = [] + let injector: TestViewSubject + + func body(content: Content) -> some View { + ZStack { + content + ForEach(hostedViews, id: \.0) { $0.1 } + } + .onReceive(injector) { hostedViews = $0 } + } +} + +#endif diff --git a/.watchOS/watchOS-Ext/watchOSApp-1.swift b/.watchOS/watchOS-Ext/watchOSApp-1.swift new file mode 100644 index 00000000..6136b5ae --- /dev/null +++ b/.watchOS/watchOS-Ext/watchOSApp-1.swift @@ -0,0 +1,22 @@ +import SwiftUI + +@main +struct watchOSApp: App { + + // swiftlint:disable weak_delegate + @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var extDelegate + // swiftlint:enable weak_delegate + + var body: some Scene { + WindowGroup { + NavigationView { + Text("Hi") + } + .testable(extDelegate.testViewSubject) + } + } +} + +final class ExtensionDelegate: NSObject, WKExtensionDelegate { + let testViewSubject = TestViewSubject([]) +} diff --git a/.watchOS/watchOS-Ext/watchOSApp-2.swift b/.watchOS/watchOS-Ext/watchOSApp-2.swift new file mode 100644 index 00000000..32071b39 --- /dev/null +++ b/.watchOS/watchOS-Ext/watchOSApp-2.swift @@ -0,0 +1,18 @@ +import WatchKit +import SwiftUI + +final class RootInterfaceController: WKHostingController> { + + let testViewSubject = TestViewSubject([]) + + override var body: RootView { + return ContentView() + .testable(testViewSubject) + } +} + +struct ContentView: View { + var body: some View { + Text("Hi") + } +} diff --git a/.watchOS/watchOS.xcodeproj/project.pbxproj b/.watchOS/watchOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..96f546fb --- /dev/null +++ b/.watchOS/watchOS.xcodeproj/project.pbxproj @@ -0,0 +1,1768 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 520DC60F26F60FBA00FCFFFD /* LazyGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58B26F60FBA00FCFFFD /* LazyGroupTests.swift */; }; + 520DC61026F60FBA00FCFFFD /* ViewHostingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58C26F60FBA00FCFFFD /* ViewHostingTests.swift */; }; + 520DC61126F60FBA00FCFFFD /* InspectionEmissaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58D26F60FBA00FCFFFD /* InspectionEmissaryTests.swift */; }; + 520DC61226F60FBA00FCFFFD /* BaseTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58E26F60FBA00FCFFFD /* BaseTypesTests.swift */; }; + 520DC61326F60FBA00FCFFFD /* ViewSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58F26F60FBA00FCFFFD /* ViewSearchTests.swift */; }; + 520DC61426F60FBA00FCFFFD /* HitTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59226F60FBA00FCFFFD /* HitTestingTests.swift */; }; + 520DC61526F60FBA00FCFFFD /* GestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59326F60FBA00FCFFFD /* GestureModifierTests.swift */; }; + 520DC61626F60FBA00FCFFFD /* GestureActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59426F60FBA00FCFFFD /* GestureActionTests.swift */; }; + 520DC61726F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59526F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift */; }; + 520DC61826F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59626F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift */; }; + 520DC61926F60FBA00FCFFFD /* ComposedGestureExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59726F60FBA00FCFFFD /* ComposedGestureExampleTests.swift */; }; + 520DC61A26F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59826F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift */; }; + 520DC61B26F60FBA00FCFFFD /* RotationGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59926F60FBA00FCFFFD /* RotationGestureTests.swift */; }; + 520DC61C26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59A26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift */; }; + 520DC61D26F60FBA00FCFFFD /* TapGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59B26F60FBA00FCFFFD /* TapGestureTests.swift */; }; + 520DC61E26F60FBA00FCFFFD /* ExclusiveGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59C26F60FBA00FCFFFD /* ExclusiveGestureTests.swift */; }; + 520DC61F26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59D26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift */; }; + 520DC62026F60FBA00FCFFFD /* SequenceGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59E26F60FBA00FCFFFD /* SequenceGestureTests.swift */; }; + 520DC62126F60FBA00FCFFFD /* GestureExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59F26F60FBA00FCFFFD /* GestureExampleTests.swift */; }; + 520DC62226F60FBA00FCFFFD /* DragGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A026F60FBA00FCFFFD /* DragGestureTests.swift */; }; + 520DC62326F60FBA00FCFFFD /* CommonComposedGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A126F60FBA00FCFFFD /* CommonComposedGestureTests.swift */; }; + 520DC62426F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A226F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift */; }; + 520DC62526F60FBB00FCFFFD /* SimultaneousGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A326F60FBA00FCFFFD /* SimultaneousGestureTests.swift */; }; + 520DC62626F60FBB00FCFFFD /* CommonComposedGestureEndedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A426F60FBA00FCFFFD /* CommonComposedGestureEndedTests.swift */; }; + 520DC62726F60FBB00FCFFFD /* CommonComposedGestureChangedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A526F60FBA00FCFFFD /* CommonComposedGestureChangedTests.swift */; }; + 520DC62826F60FBB00FCFFFD /* LongPressGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A626F60FBA00FCFFFD /* LongPressGestureTests.swift */; }; + 520DC62926F60FBB00FCFFFD /* CommonGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A726F60FBA00FCFFFD /* CommonGestureTests.swift */; }; + 520DC62A26F60FBB00FCFFFD /* MagnificationGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A826F60FBA00FCFFFD /* MagnificationGestureTests.swift */; }; + 520DC62B26F60FBB00FCFFFD /* Test.strings in Resources */ = {isa = PBXBuildFile; fileRef = 520DC5AA26F60FBA00FCFFFD /* Test.strings */; }; + 520DC62C26F60FBB00FCFFFD /* InspectableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5AE26F60FBA00FCFFFD /* InspectableViewTests.swift */; }; + 520DC62D26F60FBB00FCFFFD /* InspectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5AF26F60FBA00FCFFFD /* InspectorTests.swift */; }; + 520DC62E26F60FBB00FCFFFD /* ZStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B126F60FBA00FCFFFD /* ZStackTests.swift */; }; + 520DC62F26F60FBB00FCFFFD /* OptionalViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B226F60FBA00FCFFFD /* OptionalViewTests.swift */; }; + 520DC63026F60FBB00FCFFFD /* MenuButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B326F60FBA00FCFFFD /* MenuButtonTests.swift */; }; + 520DC63126F60FBB00FCFFFD /* TouchBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B426F60FBA00FCFFFD /* TouchBarTests.swift */; }; + 520DC63226F60FBB00FCFFFD /* GroupBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B526F60FBA00FCFFFD /* GroupBoxTests.swift */; }; + 520DC63326F60FBB00FCFFFD /* SheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B626F60FBA00FCFFFD /* SheetTests.swift */; }; + 520DC63426F60FBB00FCFFFD /* RadialGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B726F60FBA00FCFFFD /* RadialGradientTests.swift */; }; + 520DC63526F60FBB00FCFFFD /* ScrollViewReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B826F60FBA00FCFFFD /* ScrollViewReaderTests.swift */; }; + 520DC63626F60FBB00FCFFFD /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B926F60FBA00FCFFFD /* MapTests.swift */; }; + 520DC63726F60FBB00FCFFFD /* SubscriptionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BA26F60FBA00FCFFFD /* SubscriptionViewTests.swift */; }; + 520DC63826F60FBB00FCFFFD /* MenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BB26F60FBA00FCFFFD /* MenuTests.swift */; }; + 520DC63926F60FBB00FCFFFD /* LabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BC26F60FBA00FCFFFD /* LabelTests.swift */; }; + 520DC63A26F60FBB00FCFFFD /* TextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BD26F60FBA00FCFFFD /* TextTests.swift */; }; + 520DC63B26F60FBB00FCFFFD /* OpaqueViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BE26F60FBA00FCFFFD /* OpaqueViewTests.swift */; }; + 520DC63C26F60FBB00FCFFFD /* HStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BF26F60FBA00FCFFFD /* HStackTests.swift */; }; + 520DC63D26F60FBB00FCFFFD /* TreeViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C026F60FBA00FCFFFD /* TreeViewTests.swift */; }; + 520DC63E26F60FBB00FCFFFD /* EquatableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C126F60FBA00FCFFFD /* EquatableViewTests.swift */; }; + 520DC63F26F60FBB00FCFFFD /* LinearGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C226F60FBA00FCFFFD /* LinearGradientTests.swift */; }; + 520DC64026F60FBB00FCFFFD /* ToolbarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C326F60FBA00FCFFFD /* ToolbarTests.swift */; }; + 520DC64126F60FBB00FCFFFD /* DisclosureGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C426F60FBA00FCFFFD /* DisclosureGroupTests.swift */; }; + 520DC64226F60FBB00FCFFFD /* GeometryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C526F60FBA00FCFFFD /* GeometryReaderTests.swift */; }; + 520DC64326F60FBB00FCFFFD /* ProgressViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C626F60FBA00FCFFFD /* ProgressViewTests.swift */; }; + 520DC64426F60FBB00FCFFFD /* EnvironmentObjectInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C726F60FBA00FCFFFD /* EnvironmentObjectInjectionTests.swift */; }; + 520DC64526F60FBB00FCFFFD /* TabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C826F60FBA00FCFFFD /* TabViewTests.swift */; }; + 520DC64626F60FBB00FCFFFD /* PopoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C926F60FBA00FCFFFD /* PopoverTests.swift */; }; + 520DC64726F60FBB00FCFFFD /* EmptyViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CA26F60FBA00FCFFFD /* EmptyViewTests.swift */; }; + 520DC64826F60FBB00FCFFFD /* CustomViewModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CB26F60FBA00FCFFFD /* CustomViewModifierTests.swift */; }; + 520DC64926F60FBB00FCFFFD /* ImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CC26F60FBA00FCFFFD /* ImageTests.swift */; }; + 520DC64A26F60FBB00FCFFFD /* EnvironmentReaderViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CD26F60FBA00FCFFFD /* EnvironmentReaderViewTests.swift */; }; + 520DC64B26F60FBB00FCFFFD /* StepperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CE26F60FBA00FCFFFD /* StepperTests.swift */; }; + 520DC64C26F60FBB00FCFFFD /* FormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CF26F60FBA00FCFFFD /* FormTests.swift */; }; + 520DC64D26F60FBB00FCFFFD /* TupleViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D026F60FBA00FCFFFD /* TupleViewTests.swift */; }; + 520DC64E26F60FBB00FCFFFD /* ConfirmationDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D126F60FBA00FCFFFD /* ConfirmationDialogTests.swift */; }; + 520DC64F26F60FBB00FCFFFD /* LazyVGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D226F60FBA00FCFFFD /* LazyVGridTests.swift */; }; + 520DC65026F60FBB00FCFFFD /* LazyHStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D326F60FBA00FCFFFD /* LazyHStackTests.swift */; }; + 520DC65126F60FBB00FCFFFD /* AlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D426F60FBA00FCFFFD /* AlertTests.swift */; }; + 520DC65226F60FBB00FCFFFD /* TextAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D526F60FBA00FCFFFD /* TextAttributesTests.swift */; }; + 520DC65326F60FBB00FCFFFD /* PasteButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D626F60FBA00FCFFFD /* PasteButtonTests.swift */; }; + 520DC65426F60FBB00FCFFFD /* DividerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D726F60FBA00FCFFFD /* DividerTests.swift */; }; + 520DC65526F60FBB00FCFFFD /* ForEachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D826F60FBA00FCFFFD /* ForEachTests.swift */; }; + 520DC65626F60FBB00FCFFFD /* ActionSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D926F60FBA00FCFFFD /* ActionSheetTests.swift */; }; + 520DC65726F60FBB00FCFFFD /* DelayedPreferenceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DA26F60FBA00FCFFFD /* DelayedPreferenceViewTests.swift */; }; + 520DC65826F60FBB00FCFFFD /* ButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DB26F60FBA00FCFFFD /* ButtonTests.swift */; }; + 520DC65926F60FBB00FCFFFD /* LinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DC26F60FBA00FCFFFD /* LinkTests.swift */; }; + 520DC65A26F60FBB00FCFFFD /* SliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DD26F60FBA00FCFFFD /* SliderTests.swift */; }; + 520DC65B26F60FBB00FCFFFD /* CustomViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DE26F60FBA00FCFFFD /* CustomViewTests.swift */; }; + 520DC65C26F60FBB00FCFFFD /* ColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DF26F60FBA00FCFFFD /* ColorTests.swift */; }; + 520DC65D26F60FBB00FCFFFD /* TextEditorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E026F60FBA00FCFFFD /* TextEditorTests.swift */; }; + 520DC65E26F60FBB00FCFFFD /* MapAnnotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E126F60FBA00FCFFFD /* MapAnnotationTests.swift */; }; + 520DC65F26F60FBB00FCFFFD /* AngularGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E226F60FBA00FCFFFD /* AngularGradientTests.swift */; }; + 520DC66026F60FBB00FCFFFD /* ConditionalContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E326F60FBA00FCFFFD /* ConditionalContentTests.swift */; }; + 520DC66126F60FBB00FCFFFD /* VSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E426F60FBA00FCFFFD /* VSplitViewTests.swift */; }; + 520DC66226F60FBB00FCFFFD /* NavigationViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E526F60FBA00FCFFFD /* NavigationViewTests.swift */; }; + 520DC66326F60FBB00FCFFFD /* EditButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E626F60FBA00FCFFFD /* EditButtonTests.swift */; }; + 520DC66426F60FBB00FCFFFD /* IDViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E726F60FBA00FCFFFD /* IDViewTests.swift */; }; + 520DC66526F60FBB00FCFFFD /* CustomViewBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E826F60FBA00FCFFFD /* CustomViewBuilderTests.swift */; }; + 520DC66626F60FBB00FCFFFD /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E926F60FBA00FCFFFD /* TextFieldTests.swift */; }; + 520DC66726F60FBB00FCFFFD /* FullScreenCoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EA26F60FBA00FCFFFD /* FullScreenCoverTests.swift */; }; + 520DC66826F60FBB00FCFFFD /* NavigationLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EB26F60FBA00FCFFFD /* NavigationLinkTests.swift */; }; + 520DC66926F60FBB00FCFFFD /* VStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EC26F60FBA00FCFFFD /* VStackTests.swift */; }; + 520DC66A26F60FBB00FCFFFD /* PickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5ED26F60FBA00FCFFFD /* PickerTests.swift */; }; + 520DC66B26F60FBB00FCFFFD /* GroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EE26F60FBA00FCFFFD /* GroupTests.swift */; }; + 520DC66C26F60FBB00FCFFFD /* SectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EF26F60FBA00FCFFFD /* SectionTests.swift */; }; + 520DC66D26F60FBB00FCFFFD /* ToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F026F60FBA00FCFFFD /* ToggleTests.swift */; }; + 520DC66E26F60FBB00FCFFFD /* SpacerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F126F60FBA00FCFFFD /* SpacerTests.swift */; }; + 520DC66F26F60FBB00FCFFFD /* DatePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F226F60FBA00FCFFFD /* DatePickerTests.swift */; }; + 520DC67026F60FBB00FCFFFD /* SafeAreaInsetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F326F60FBA00FCFFFD /* SafeAreaInsetTests.swift */; }; + 520DC67126F60FBB00FCFFFD /* ColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F426F60FBA00FCFFFD /* ColorPickerTests.swift */; }; + 520DC67226F60FBB00FCFFFD /* OutlineGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F526F60FBA00FCFFFD /* OutlineGroupTests.swift */; }; + 520DC67326F60FBB00FCFFFD /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F626F60FBA00FCFFFD /* ScrollViewTests.swift */; }; + 520DC67426F60FBB00FCFFFD /* SecureFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F726F60FBA00FCFFFD /* SecureFieldTests.swift */; }; + 520DC67526F60FBB00FCFFFD /* LazyHGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F826F60FBA00FCFFFD /* LazyHGridTests.swift */; }; + 520DC67626F60FBB00FCFFFD /* ShapeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F926F60FBA00FCFFFD /* ShapeTests.swift */; }; + 520DC67726F60FBB00FCFFFD /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FA26F60FBA00FCFFFD /* ListTests.swift */; }; + 520DC67826F60FBB00FCFFFD /* HSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FB26F60FBA00FCFFFD /* HSplitViewTests.swift */; }; + 520DC67926F60FBB00FCFFFD /* AnyViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FC26F60FBA00FCFFFD /* AnyViewTests.swift */; }; + 520DC67A26F60FBB00FCFFFD /* LazyVStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FD26F60FBA00FCFFFD /* LazyVStackTests.swift */; }; + 520DC67B26F60FBB00FCFFFD /* ConfigurationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FF26F60FBA00FCFFFD /* ConfigurationModifiersTests.swift */; }; + 520DC67C26F60FBB00FCFFFD /* PresentationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60026F60FBA00FCFFFD /* PresentationModifiersTests.swift */; }; + 520DC67D26F60FBB00FCFFFD /* InteractionModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60126F60FBA00FCFFFD /* InteractionModifiersTests.swift */; }; + 520DC67E26F60FBB00FCFFFD /* EventsModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60226F60FBA00FCFFFD /* EventsModifiersTests.swift */; }; + 520DC67F26F60FBB00FCFFFD /* SizingModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60326F60FBA00FCFFFD /* SizingModifiersTests.swift */; }; + 520DC68026F60FBB00FCFFFD /* NestedModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60426F60FBA00FCFFFD /* NestedModifiersTests.swift */; }; + 520DC68126F60FBB00FCFFFD /* PositioningModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60526F60FBA00FCFFFD /* PositioningModifiersTests.swift */; }; + 520DC68226F60FBB00FCFFFD /* TextInputModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60626F60FBA00FCFFFD /* TextInputModifiersTests.swift */; }; + 520DC68326F60FBB00FCFFFD /* CustomStyleModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60726F60FBA00FCFFFD /* CustomStyleModifiersTests.swift */; }; + 520DC68426F60FBB00FCFFFD /* TransitiveModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60826F60FBA00FCFFFD /* TransitiveModifiersTests.swift */; }; + 520DC68526F60FBB00FCFFFD /* AccessibilityModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60926F60FBA00FCFFFD /* AccessibilityModifiersTests.swift */; }; + 520DC68626F60FBB00FCFFFD /* AnimationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60A26F60FBA00FCFFFD /* AnimationModifiersTests.swift */; }; + 520DC68726F60FBB00FCFFFD /* VisualEffectModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60B26F60FBA00FCFFFD /* VisualEffectModifiersTests.swift */; }; + 520DC68826F60FBB00FCFFFD /* EnvironmentModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60C26F60FBA00FCFFFD /* EnvironmentModifiersTests.swift */; }; + 520DC68926F60FBB00FCFFFD /* ViewPaddingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60D26F60FBA00FCFFFD /* ViewPaddingTests.swift */; }; + 520DC68A26F60FBB00FCFFFD /* TransformingModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60E26F60FBA00FCFFFD /* TransformingModifiersTests.swift */; }; + 520DC69026F60FF400FCFFFD /* AlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D426F60FBA00FCFFFD /* AlertTests.swift */; }; + 520DC69126F60FF400FCFFFD /* InspectableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5AE26F60FBA00FCFFFD /* InspectableViewTests.swift */; }; + 520DC69226F60FF400FCFFFD /* ExclusiveGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59C26F60FBA00FCFFFD /* ExclusiveGestureTests.swift */; }; + 520DC69326F60FF400FCFFFD /* MenuButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B326F60FBA00FCFFFD /* MenuButtonTests.swift */; }; + 520DC69426F60FF400FCFFFD /* EnvironmentObjectInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C726F60FBA00FCFFFD /* EnvironmentObjectInjectionTests.swift */; }; + 520DC69526F60FF400FCFFFD /* LazyVStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FD26F60FBA00FCFFFD /* LazyVStackTests.swift */; }; + 520DC69626F60FF400FCFFFD /* MapAnnotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E126F60FBA00FCFFFD /* MapAnnotationTests.swift */; }; + 520DC69726F60FF400FCFFFD /* MenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BB26F60FBA00FCFFFD /* MenuTests.swift */; }; + 520DC69826F60FF400FCFFFD /* PresentationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60026F60FBA00FCFFFD /* PresentationModifiersTests.swift */; }; + 520DC69926F60FF400FCFFFD /* SliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DD26F60FBA00FCFFFD /* SliderTests.swift */; }; + 520DC69A26F60FF400FCFFFD /* PopoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C926F60FBA00FCFFFD /* PopoverTests.swift */; }; + 520DC69B26F60FF400FCFFFD /* ActionSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D926F60FBA00FCFFFD /* ActionSheetTests.swift */; }; + 520DC69C26F60FF400FCFFFD /* SpacerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F126F60FBA00FCFFFD /* SpacerTests.swift */; }; + 520DC69D26F60FF400FCFFFD /* RadialGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B726F60FBA00FCFFFD /* RadialGradientTests.swift */; }; + 520DC69E26F60FF400FCFFFD /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E926F60FBA00FCFFFD /* TextFieldTests.swift */; }; + 520DC69F26F60FF400FCFFFD /* LinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DC26F60FBA00FCFFFD /* LinkTests.swift */; }; + 520DC6A026F60FF400FCFFFD /* EventsModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60226F60FBA00FCFFFD /* EventsModifiersTests.swift */; }; + 520DC6A126F60FF400FCFFFD /* StepperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CE26F60FBA00FCFFFD /* StepperTests.swift */; }; + 520DC6A226F60FF400FCFFFD /* ConfirmationDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D126F60FBA00FCFFFD /* ConfirmationDialogTests.swift */; }; + 520DC6A326F60FF400FCFFFD /* HSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FB26F60FBA00FCFFFD /* HSplitViewTests.swift */; }; + 520DC6A426F60FF400FCFFFD /* CustomViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DE26F60FBA00FCFFFD /* CustomViewTests.swift */; }; + 520DC6A526F60FF400FCFFFD /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FA26F60FBA00FCFFFD /* ListTests.swift */; }; + 520DC6A626F60FF400FCFFFD /* HitTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59226F60FBA00FCFFFD /* HitTestingTests.swift */; }; + 520DC6A726F60FF400FCFFFD /* SectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EF26F60FBA00FCFFFD /* SectionTests.swift */; }; + 520DC6A826F60FF400FCFFFD /* VSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E426F60FBA00FCFFFD /* VSplitViewTests.swift */; }; + 520DC6A926F60FF400FCFFFD /* EnvironmentModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60C26F60FBA00FCFFFD /* EnvironmentModifiersTests.swift */; }; + 520DC6AA26F60FF400FCFFFD /* SecureFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F726F60FBA00FCFFFD /* SecureFieldTests.swift */; }; + 520DC6AB26F60FF400FCFFFD /* VisualEffectModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60B26F60FBA00FCFFFD /* VisualEffectModifiersTests.swift */; }; + 520DC6AC26F60FF400FCFFFD /* AnyViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FC26F60FBA00FCFFFD /* AnyViewTests.swift */; }; + 520DC6AD26F60FF400FCFFFD /* AngularGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E226F60FBA00FCFFFD /* AngularGradientTests.swift */; }; + 520DC6AE26F60FF400FCFFFD /* PickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5ED26F60FBA00FCFFFD /* PickerTests.swift */; }; + 520DC6AF26F60FF400FCFFFD /* SimultaneousGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A326F60FBA00FCFFFD /* SimultaneousGestureTests.swift */; }; + 520DC6B026F60FF400FCFFFD /* SizingModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60326F60FBA00FCFFFD /* SizingModifiersTests.swift */; }; + 520DC6B126F60FF400FCFFFD /* CustomViewBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E826F60FBA00FCFFFD /* CustomViewBuilderTests.swift */; }; + 520DC6B226F60FF400FCFFFD /* ZStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B126F60FBA00FCFFFD /* ZStackTests.swift */; }; + 520DC6B326F60FF400FCFFFD /* CommonComposedGestureChangedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A526F60FBA00FCFFFD /* CommonComposedGestureChangedTests.swift */; }; + 520DC6B426F60FF400FCFFFD /* EditButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E626F60FBA00FCFFFD /* EditButtonTests.swift */; }; + 520DC6B526F60FF400FCFFFD /* OpaqueViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BE26F60FBA00FCFFFD /* OpaqueViewTests.swift */; }; + 520DC6B626F60FF400FCFFFD /* EmptyViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CA26F60FBA00FCFFFD /* EmptyViewTests.swift */; }; + 520DC6B726F60FF400FCFFFD /* OptionalViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B226F60FBA00FCFFFD /* OptionalViewTests.swift */; }; + 520DC6B826F60FF400FCFFFD /* ConfigurationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5FF26F60FBA00FCFFFD /* ConfigurationModifiersTests.swift */; }; + 520DC6B926F60FF400FCFFFD /* InteractionModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60126F60FBA00FCFFFD /* InteractionModifiersTests.swift */; }; + 520DC6BA26F60FF400FCFFFD /* PositioningModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60526F60FBA00FCFFFD /* PositioningModifiersTests.swift */; }; + 520DC6BB26F60FF400FCFFFD /* NestedModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60426F60FBA00FCFFFD /* NestedModifiersTests.swift */; }; + 520DC6BC26F60FF400FCFFFD /* TransitiveModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60826F60FBA00FCFFFD /* TransitiveModifiersTests.swift */; }; + 520DC6BD26F60FF400FCFFFD /* AnimationModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60A26F60FBA00FCFFFD /* AnimationModifiersTests.swift */; }; + 520DC6BE26F60FF400FCFFFD /* IDViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E726F60FBA00FCFFFD /* IDViewTests.swift */; }; + 520DC6BF26F60FF400FCFFFD /* SubscriptionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BA26F60FBA00FCFFFD /* SubscriptionViewTests.swift */; }; + 520DC6C026F60FF400FCFFFD /* SequenceGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59826F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift */; }; + 520DC6C126F60FF400FCFFFD /* TextEditorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E026F60FBA00FCFFFD /* TextEditorTests.swift */; }; + 520DC6C226F60FF400FCFFFD /* SimultaneousGestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59626F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift */; }; + 520DC6C326F60FF400FCFFFD /* HighPriorityGestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59526F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift */; }; + 520DC6C426F60FF400FCFFFD /* ShapeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F926F60FBA00FCFFFD /* ShapeTests.swift */; }; + 520DC6C526F60FF400FCFFFD /* ToolbarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C326F60FBA00FCFFFD /* ToolbarTests.swift */; }; + 520DC6C626F60FF400FCFFFD /* HStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BF26F60FBA00FCFFFD /* HStackTests.swift */; }; + 520DC6C726F60FF400FCFFFD /* TouchBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B426F60FBA00FCFFFD /* TouchBarTests.swift */; }; + 520DC6C826F60FF400FCFFFD /* CustomViewModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CB26F60FBA00FCFFFD /* CustomViewModifierTests.swift */; }; + 520DC6C926F60FF400FCFFFD /* TextInputModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60626F60FBA00FCFFFD /* TextInputModifiersTests.swift */; }; + 520DC6CA26F60FF400FCFFFD /* GestureExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59F26F60FBA00FCFFFD /* GestureExampleTests.swift */; }; + 520DC6CB26F60FF400FCFFFD /* SimultaneousGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59D26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift */; }; + 520DC6CC26F60FF400FCFFFD /* GestureModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59326F60FBA00FCFFFD /* GestureModifierTests.swift */; }; + 520DC6CD26F60FF400FCFFFD /* ImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CC26F60FBA00FCFFFD /* ImageTests.swift */; }; + 520DC6CE26F60FF400FCFFFD /* ColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DF26F60FBA00FCFFFD /* ColorTests.swift */; }; + 520DC6CF26F60FF400FCFFFD /* ButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DB26F60FBA00FCFFFD /* ButtonTests.swift */; }; + 520DC6D026F60FF400FCFFFD /* SheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B626F60FBA00FCFFFD /* SheetTests.swift */; }; + 520DC6D126F60FF400FCFFFD /* CommonComposedGestureUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A226F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift */; }; + 520DC6D226F60FF400FCFFFD /* EnvironmentReaderViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CD26F60FBA00FCFFFD /* EnvironmentReaderViewTests.swift */; }; + 520DC6D326F60FF400FCFFFD /* ColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F426F60FBA00FCFFFD /* ColorPickerTests.swift */; }; + 520DC6D426F60FF400FCFFFD /* OutlineGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F526F60FBA00FCFFFD /* OutlineGroupTests.swift */; }; + 520DC6D526F60FF400FCFFFD /* InspectionEmissaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58D26F60FBA00FCFFFD /* InspectionEmissaryTests.swift */; }; + 520DC6D626F60FF400FCFFFD /* ToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F026F60FBA00FCFFFD /* ToggleTests.swift */; }; + 520DC6D726F60FF400FCFFFD /* DragGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A026F60FBA00FCFFFD /* DragGestureTests.swift */; }; + 520DC6D826F60FF400FCFFFD /* ConditionalContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E326F60FBA00FCFFFD /* ConditionalContentTests.swift */; }; + 520DC6D926F60FF400FCFFFD /* TransformingModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60E26F60FBA00FCFFFD /* TransformingModifiersTests.swift */; }; + 520DC6DA26F60FF400FCFFFD /* ProgressViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C626F60FBA00FCFFFD /* ProgressViewTests.swift */; }; + 520DC6DB26F60FF400FCFFFD /* GroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EE26F60FBA00FCFFFD /* GroupTests.swift */; }; + 520DC6DC26F60FF400FCFFFD /* TupleViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D026F60FBA00FCFFFD /* TupleViewTests.swift */; }; + 520DC6DD26F60FF400FCFFFD /* AccessibilityModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60926F60FBA00FCFFFD /* AccessibilityModifiersTests.swift */; }; + 520DC6DE26F60FF400FCFFFD /* ScrollViewReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B826F60FBA00FCFFFD /* ScrollViewReaderTests.swift */; }; + 520DC6DF26F60FF400FCFFFD /* LongPressGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A626F60FBA00FCFFFD /* LongPressGestureTests.swift */; }; + 520DC6E026F60FF400FCFFFD /* SafeAreaInsetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F326F60FBA00FCFFFD /* SafeAreaInsetTests.swift */; }; + 520DC6E126F60FF400FCFFFD /* ViewPaddingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60D26F60FBA00FCFFFD /* ViewPaddingTests.swift */; }; + 520DC6E226F60FF400FCFFFD /* CustomStyleModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC60726F60FBA00FCFFFD /* CustomStyleModifiersTests.swift */; }; + 520DC6E326F60FF400FCFFFD /* InspectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5AF26F60FBA00FCFFFD /* InspectorTests.swift */; }; + 520DC6E426F60FF400FCFFFD /* TextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BD26F60FBA00FCFFFD /* TextTests.swift */; }; + 520DC6E526F60FF400FCFFFD /* TapGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59B26F60FBA00FCFFFD /* TapGestureTests.swift */; }; + 520DC6E626F60FF400FCFFFD /* CommonComposedGestureEndedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A426F60FBA00FCFFFD /* CommonComposedGestureEndedTests.swift */; }; + 520DC6E726F60FF400FCFFFD /* CommonGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A726F60FBA00FCFFFD /* CommonGestureTests.swift */; }; + 520DC6E826F60FF400FCFFFD /* DatePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F226F60FBA00FCFFFD /* DatePickerTests.swift */; }; + 520DC6E926F60FF400FCFFFD /* FullScreenCoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EA26F60FBA00FCFFFD /* FullScreenCoverTests.swift */; }; + 520DC6EA26F60FF400FCFFFD /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B926F60FBA00FCFFFD /* MapTests.swift */; }; + 520DC6EB26F60FF400FCFFFD /* LinearGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C226F60FBA00FCFFFD /* LinearGradientTests.swift */; }; + 520DC6EC26F60FF400FCFFFD /* FormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5CF26F60FBA00FCFFFD /* FormTests.swift */; }; + 520DC6ED26F60FF400FCFFFD /* EquatableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C126F60FBA00FCFFFD /* EquatableViewTests.swift */; }; + 520DC6EE26F60FF400FCFFFD /* DelayedPreferenceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5DA26F60FBA00FCFFFD /* DelayedPreferenceViewTests.swift */; }; + 520DC6EF26F60FF400FCFFFD /* LazyHGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F826F60FBA00FCFFFD /* LazyHGridTests.swift */; }; + 520DC6F026F60FF400FCFFFD /* GestureActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59426F60FBA00FCFFFD /* GestureActionTests.swift */; }; + 520DC6F126F60FF400FCFFFD /* ComposedGestureExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59726F60FBA00FCFFFD /* ComposedGestureExampleTests.swift */; }; + 520DC6F226F60FF400FCFFFD /* PasteButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D626F60FBA00FCFFFD /* PasteButtonTests.swift */; }; + 520DC6F326F60FF400FCFFFD /* ForEachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D826F60FBA00FCFFFD /* ForEachTests.swift */; }; + 520DC6F426F60FF400FCFFFD /* NavigationLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EB26F60FBA00FCFFFD /* NavigationLinkTests.swift */; }; + 520DC6F526F60FF400FCFFFD /* DividerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D726F60FBA00FCFFFD /* DividerTests.swift */; }; + 520DC6F626F60FF400FCFFFD /* LazyGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58B26F60FBA00FCFFFD /* LazyGroupTests.swift */; }; + 520DC6F726F60FF400FCFFFD /* TabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C826F60FBA00FCFFFD /* TabViewTests.swift */; }; + 520DC6F826F60FF400FCFFFD /* BaseTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58E26F60FBA00FCFFFD /* BaseTypesTests.swift */; }; + 520DC6F926F60FF400FCFFFD /* RotationGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59926F60FBA00FCFFFD /* RotationGestureTests.swift */; }; + 520DC6FA26F60FF400FCFFFD /* NavigationViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5E526F60FBA00FCFFFD /* NavigationViewTests.swift */; }; + 520DC6FB26F60FF400FCFFFD /* ExclusiveGestureChildrenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59A26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift */; }; + 520DC6FC26F60FF400FCFFFD /* ViewHostingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58C26F60FBA00FCFFFD /* ViewHostingTests.swift */; }; + 520DC6FD26F60FF400FCFFFD /* GroupBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5B526F60FBA00FCFFFD /* GroupBoxTests.swift */; }; + 520DC6FE26F60FF400FCFFFD /* CommonComposedGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A126F60FBA00FCFFFD /* CommonComposedGestureTests.swift */; }; + 520DC6FF26F60FF400FCFFFD /* LabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5BC26F60FBA00FCFFFD /* LabelTests.swift */; }; + 520DC70026F60FF400FCFFFD /* MagnificationGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5A826F60FBA00FCFFFD /* MagnificationGestureTests.swift */; }; + 520DC70126F60FF400FCFFFD /* VStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5EC26F60FBA00FCFFFD /* VStackTests.swift */; }; + 520DC70226F60FF400FCFFFD /* SequenceGestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC59E26F60FBA00FCFFFD /* SequenceGestureTests.swift */; }; + 520DC70326F60FF400FCFFFD /* TextAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D526F60FBA00FCFFFD /* TextAttributesTests.swift */; }; + 520DC70426F60FF400FCFFFD /* ScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5F626F60FBA00FCFFFD /* ScrollViewTests.swift */; }; + 520DC70526F60FF400FCFFFD /* TreeViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C026F60FBA00FCFFFD /* TreeViewTests.swift */; }; + 520DC70626F60FF400FCFFFD /* LazyHStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D326F60FBA00FCFFFD /* LazyHStackTests.swift */; }; + 520DC70726F60FF400FCFFFD /* DisclosureGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C426F60FBA00FCFFFD /* DisclosureGroupTests.swift */; }; + 520DC70826F60FF400FCFFFD /* ViewSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC58F26F60FBA00FCFFFD /* ViewSearchTests.swift */; }; + 520DC70926F60FF400FCFFFD /* LazyVGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5D226F60FBA00FCFFFD /* LazyVGridTests.swift */; }; + 520DC70A26F60FF400FCFFFD /* GeometryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520DC5C526F60FBA00FCFFFD /* GeometryReaderTests.swift */; }; + 520DC70C26F60FF400FCFFFD /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 520DC68E26F60FF400FCFFFD /* ViewInspector */; }; + 520DC70E26F60FF400FCFFFD /* Test.strings in Resources */ = {isa = PBXBuildFile; fileRef = 520DC5AA26F60FBA00FCFFFD /* Test.strings */; }; + 520DC71526F6115D00FCFFFD /* watchOSApp+Testable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CA301526ECD64400BFD568 /* watchOSApp+Testable.swift */; }; + 5293010626EFC96600012E90 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5293010426EFC96600012E90 /* Interface.storyboard */; }; + 5293010E26EFC96700012E90 /* watchOS-Ext-2.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5293010D26EFC96700012E90 /* watchOS-Ext-2.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 5293012726EFCBD300012E90 /* watchOSApp-2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5293011426EFC96700012E90 /* watchOSApp-2.swift */; }; + 52CA301426ECC5D200BFD568 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 52CA301326ECC5D200BFD568 /* ViewInspector */; }; + 52CA301626ECD64400BFD568 /* watchOSApp+Testable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CA301526ECD64400BFD568 /* watchOSApp+Testable.swift */; }; + 52E3259B26C72E7900CCE47E /* watchOS-Ext-1.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 52E3259A26C72E7900CCE47E /* watchOS-Ext-1.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 52E325A226C72E7900CCE47E /* watchOSApp-1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E325A126C72E7900CCE47E /* watchOSApp-1.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 520213A826ECB45D00E94C6E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52E3258426C72E7800CCE47E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52E3259926C72E7900CCE47E; + remoteInfo = "watchOS-Ext"; + }; + 520DC68D26F60FF400FCFFFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52E3258426C72E7800CCE47E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52E3259926C72E7900CCE47E; + remoteInfo = "watchOS-Ext"; + }; + 520DC71326F6103300FCFFFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52E3258426C72E7800CCE47E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5293010C26EFC96700012E90; + remoteInfo = "watchOS-Ext-2"; + }; + 5293010F26EFC96700012E90 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52E3258426C72E7800CCE47E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5293010C26EFC96700012E90; + remoteInfo = "watchOSApp-2 WatchKit Extension"; + }; + 52E3259C26C72E7900CCE47E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52E3258426C72E7800CCE47E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52E3259926C72E7900CCE47E; + remoteInfo = "watchOS WatchKit Extension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 5293011E26EFC96800012E90 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 5293010E26EFC96700012E90 /* watchOS-Ext-2.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 52E325B026C72E7900CCE47E /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 52E3259B26C72E7900CCE47E /* watchOS-Ext-1.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 520213A426ECB45D00E94C6E /* watchOS-1-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "watchOS-1-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 520DC58B26F60FBA00FCFFFD /* LazyGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyGroupTests.swift; sourceTree = ""; }; + 520DC58C26F60FBA00FCFFFD /* ViewHostingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewHostingTests.swift; sourceTree = ""; }; + 520DC58D26F60FBA00FCFFFD /* InspectionEmissaryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InspectionEmissaryTests.swift; sourceTree = ""; }; + 520DC58E26F60FBA00FCFFFD /* BaseTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTypesTests.swift; sourceTree = ""; }; + 520DC58F26F60FBA00FCFFFD /* ViewSearchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewSearchTests.swift; sourceTree = ""; }; + 520DC59226F60FBA00FCFFFD /* HitTestingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HitTestingTests.swift; sourceTree = ""; }; + 520DC59326F60FBA00FCFFFD /* GestureModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureModifierTests.swift; sourceTree = ""; }; + 520DC59426F60FBA00FCFFFD /* GestureActionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureActionTests.swift; sourceTree = ""; }; + 520DC59526F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighPriorityGestureModifierTests.swift; sourceTree = ""; }; + 520DC59626F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimultaneousGestureModifierTests.swift; sourceTree = ""; }; + 520DC59726F60FBA00FCFFFD /* ComposedGestureExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposedGestureExampleTests.swift; sourceTree = ""; }; + 520DC59826F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceGestureChildrenTests.swift; sourceTree = ""; }; + 520DC59926F60FBA00FCFFFD /* RotationGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotationGestureTests.swift; sourceTree = ""; }; + 520DC59A26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExclusiveGestureChildrenTests.swift; sourceTree = ""; }; + 520DC59B26F60FBA00FCFFFD /* TapGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TapGestureTests.swift; sourceTree = ""; }; + 520DC59C26F60FBA00FCFFFD /* ExclusiveGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExclusiveGestureTests.swift; sourceTree = ""; }; + 520DC59D26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimultaneousGestureChildrenTests.swift; sourceTree = ""; }; + 520DC59E26F60FBA00FCFFFD /* SequenceGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceGestureTests.swift; sourceTree = ""; }; + 520DC59F26F60FBA00FCFFFD /* GestureExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureExampleTests.swift; sourceTree = ""; }; + 520DC5A026F60FBA00FCFFFD /* DragGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureTests.swift; sourceTree = ""; }; + 520DC5A126F60FBA00FCFFFD /* CommonComposedGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureTests.swift; sourceTree = ""; }; + 520DC5A226F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureUpdatingTests.swift; sourceTree = ""; }; + 520DC5A326F60FBA00FCFFFD /* SimultaneousGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimultaneousGestureTests.swift; sourceTree = ""; }; + 520DC5A426F60FBA00FCFFFD /* CommonComposedGestureEndedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureEndedTests.swift; sourceTree = ""; }; + 520DC5A526F60FBA00FCFFFD /* CommonComposedGestureChangedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureChangedTests.swift; sourceTree = ""; }; + 520DC5A626F60FBA00FCFFFD /* LongPressGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LongPressGestureTests.swift; sourceTree = ""; }; + 520DC5A726F60FBA00FCFFFD /* CommonGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonGestureTests.swift; sourceTree = ""; }; + 520DC5A826F60FBA00FCFFFD /* MagnificationGestureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagnificationGestureTests.swift; sourceTree = ""; }; + 520DC5AB26F60FBA00FCFFFD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Test.strings; sourceTree = ""; }; + 520DC5AC26F60FBA00FCFFFD /* en-AU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-AU"; path = "en-AU.lproj/Test.strings"; sourceTree = ""; }; + 520DC5AD26F60FBA00FCFFFD /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Test.strings; sourceTree = ""; }; + 520DC5AE26F60FBA00FCFFFD /* InspectableViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InspectableViewTests.swift; sourceTree = ""; }; + 520DC5AF26F60FBA00FCFFFD /* InspectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InspectorTests.swift; sourceTree = ""; }; + 520DC5B126F60FBA00FCFFFD /* ZStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZStackTests.swift; sourceTree = ""; }; + 520DC5B226F60FBA00FCFFFD /* OptionalViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalViewTests.swift; sourceTree = ""; }; + 520DC5B326F60FBA00FCFFFD /* MenuButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuButtonTests.swift; sourceTree = ""; }; + 520DC5B426F60FBA00FCFFFD /* TouchBarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchBarTests.swift; sourceTree = ""; }; + 520DC5B526F60FBA00FCFFFD /* GroupBoxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupBoxTests.swift; sourceTree = ""; }; + 520DC5B626F60FBA00FCFFFD /* SheetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SheetTests.swift; sourceTree = ""; }; + 520DC5B726F60FBA00FCFFFD /* RadialGradientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadialGradientTests.swift; sourceTree = ""; }; + 520DC5B826F60FBA00FCFFFD /* ScrollViewReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewReaderTests.swift; sourceTree = ""; }; + 520DC5B926F60FBA00FCFFFD /* MapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; + 520DC5BA26F60FBA00FCFFFD /* SubscriptionViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionViewTests.swift; sourceTree = ""; }; + 520DC5BB26F60FBA00FCFFFD /* MenuTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuTests.swift; sourceTree = ""; }; + 520DC5BC26F60FBA00FCFFFD /* LabelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelTests.swift; sourceTree = ""; }; + 520DC5BD26F60FBA00FCFFFD /* TextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextTests.swift; sourceTree = ""; }; + 520DC5BE26F60FBA00FCFFFD /* OpaqueViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpaqueViewTests.swift; sourceTree = ""; }; + 520DC5BF26F60FBA00FCFFFD /* HStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HStackTests.swift; sourceTree = ""; }; + 520DC5C026F60FBA00FCFFFD /* TreeViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeViewTests.swift; sourceTree = ""; }; + 520DC5C126F60FBA00FCFFFD /* EquatableViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquatableViewTests.swift; sourceTree = ""; }; + 520DC5C226F60FBA00FCFFFD /* LinearGradientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearGradientTests.swift; sourceTree = ""; }; + 520DC5C326F60FBA00FCFFFD /* ToolbarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarTests.swift; sourceTree = ""; }; + 520DC5C426F60FBA00FCFFFD /* DisclosureGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisclosureGroupTests.swift; sourceTree = ""; }; + 520DC5C526F60FBA00FCFFFD /* GeometryReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeometryReaderTests.swift; sourceTree = ""; }; + 520DC5C626F60FBA00FCFFFD /* ProgressViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressViewTests.swift; sourceTree = ""; }; + 520DC5C726F60FBA00FCFFFD /* EnvironmentObjectInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentObjectInjectionTests.swift; sourceTree = ""; }; + 520DC5C826F60FBA00FCFFFD /* TabViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewTests.swift; sourceTree = ""; }; + 520DC5C926F60FBA00FCFFFD /* PopoverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverTests.swift; sourceTree = ""; }; + 520DC5CA26F60FBA00FCFFFD /* EmptyViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyViewTests.swift; sourceTree = ""; }; + 520DC5CB26F60FBA00FCFFFD /* CustomViewModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomViewModifierTests.swift; sourceTree = ""; }; + 520DC5CC26F60FBA00FCFFFD /* ImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageTests.swift; sourceTree = ""; }; + 520DC5CD26F60FBA00FCFFFD /* EnvironmentReaderViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentReaderViewTests.swift; sourceTree = ""; }; + 520DC5CE26F60FBA00FCFFFD /* StepperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepperTests.swift; sourceTree = ""; }; + 520DC5CF26F60FBA00FCFFFD /* FormTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormTests.swift; sourceTree = ""; }; + 520DC5D026F60FBA00FCFFFD /* TupleViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TupleViewTests.swift; sourceTree = ""; }; + 520DC5D126F60FBA00FCFFFD /* ConfirmationDialogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmationDialogTests.swift; sourceTree = ""; }; + 520DC5D226F60FBA00FCFFFD /* LazyVGridTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyVGridTests.swift; sourceTree = ""; }; + 520DC5D326F60FBA00FCFFFD /* LazyHStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyHStackTests.swift; sourceTree = ""; }; + 520DC5D426F60FBA00FCFFFD /* AlertTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertTests.swift; sourceTree = ""; }; + 520DC5D526F60FBA00FCFFFD /* TextAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextAttributesTests.swift; sourceTree = ""; }; + 520DC5D626F60FBA00FCFFFD /* PasteButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasteButtonTests.swift; sourceTree = ""; }; + 520DC5D726F60FBA00FCFFFD /* DividerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DividerTests.swift; sourceTree = ""; }; + 520DC5D826F60FBA00FCFFFD /* ForEachTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForEachTests.swift; sourceTree = ""; }; + 520DC5D926F60FBA00FCFFFD /* ActionSheetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetTests.swift; sourceTree = ""; }; + 520DC5DA26F60FBA00FCFFFD /* DelayedPreferenceViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayedPreferenceViewTests.swift; sourceTree = ""; }; + 520DC5DB26F60FBA00FCFFFD /* ButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonTests.swift; sourceTree = ""; }; + 520DC5DC26F60FBA00FCFFFD /* LinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkTests.swift; sourceTree = ""; }; + 520DC5DD26F60FBA00FCFFFD /* SliderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTests.swift; sourceTree = ""; }; + 520DC5DE26F60FBA00FCFFFD /* CustomViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomViewTests.swift; sourceTree = ""; }; + 520DC5DF26F60FBA00FCFFFD /* ColorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorTests.swift; sourceTree = ""; }; + 520DC5E026F60FBA00FCFFFD /* TextEditorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEditorTests.swift; sourceTree = ""; }; + 520DC5E126F60FBA00FCFFFD /* MapAnnotationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapAnnotationTests.swift; sourceTree = ""; }; + 520DC5E226F60FBA00FCFFFD /* AngularGradientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AngularGradientTests.swift; sourceTree = ""; }; + 520DC5E326F60FBA00FCFFFD /* ConditionalContentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalContentTests.swift; sourceTree = ""; }; + 520DC5E426F60FBA00FCFFFD /* VSplitViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VSplitViewTests.swift; sourceTree = ""; }; + 520DC5E526F60FBA00FCFFFD /* NavigationViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationViewTests.swift; sourceTree = ""; }; + 520DC5E626F60FBA00FCFFFD /* EditButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditButtonTests.swift; sourceTree = ""; }; + 520DC5E726F60FBA00FCFFFD /* IDViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IDViewTests.swift; sourceTree = ""; }; + 520DC5E826F60FBA00FCFFFD /* CustomViewBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomViewBuilderTests.swift; sourceTree = ""; }; + 520DC5E926F60FBA00FCFFFD /* TextFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTests.swift; sourceTree = ""; }; + 520DC5EA26F60FBA00FCFFFD /* FullScreenCoverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullScreenCoverTests.swift; sourceTree = ""; }; + 520DC5EB26F60FBA00FCFFFD /* NavigationLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinkTests.swift; sourceTree = ""; }; + 520DC5EC26F60FBA00FCFFFD /* VStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VStackTests.swift; sourceTree = ""; }; + 520DC5ED26F60FBA00FCFFFD /* PickerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerTests.swift; sourceTree = ""; }; + 520DC5EE26F60FBA00FCFFFD /* GroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupTests.swift; sourceTree = ""; }; + 520DC5EF26F60FBA00FCFFFD /* SectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionTests.swift; sourceTree = ""; }; + 520DC5F026F60FBA00FCFFFD /* ToggleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleTests.swift; sourceTree = ""; }; + 520DC5F126F60FBA00FCFFFD /* SpacerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpacerTests.swift; sourceTree = ""; }; + 520DC5F226F60FBA00FCFFFD /* DatePickerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerTests.swift; sourceTree = ""; }; + 520DC5F326F60FBA00FCFFFD /* SafeAreaInsetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeAreaInsetTests.swift; sourceTree = ""; }; + 520DC5F426F60FBA00FCFFFD /* ColorPickerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPickerTests.swift; sourceTree = ""; }; + 520DC5F526F60FBA00FCFFFD /* OutlineGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineGroupTests.swift; sourceTree = ""; }; + 520DC5F626F60FBA00FCFFFD /* ScrollViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewTests.swift; sourceTree = ""; }; + 520DC5F726F60FBA00FCFFFD /* SecureFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureFieldTests.swift; sourceTree = ""; }; + 520DC5F826F60FBA00FCFFFD /* LazyHGridTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyHGridTests.swift; sourceTree = ""; }; + 520DC5F926F60FBA00FCFFFD /* ShapeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeTests.swift; sourceTree = ""; }; + 520DC5FA26F60FBA00FCFFFD /* ListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = ""; }; + 520DC5FB26F60FBA00FCFFFD /* HSplitViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HSplitViewTests.swift; sourceTree = ""; }; + 520DC5FC26F60FBA00FCFFFD /* AnyViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyViewTests.swift; sourceTree = ""; }; + 520DC5FD26F60FBA00FCFFFD /* LazyVStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyVStackTests.swift; sourceTree = ""; }; + 520DC5FF26F60FBA00FCFFFD /* ConfigurationModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationModifiersTests.swift; sourceTree = ""; }; + 520DC60026F60FBA00FCFFFD /* PresentationModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationModifiersTests.swift; sourceTree = ""; }; + 520DC60126F60FBA00FCFFFD /* InteractionModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractionModifiersTests.swift; sourceTree = ""; }; + 520DC60226F60FBA00FCFFFD /* EventsModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsModifiersTests.swift; sourceTree = ""; }; + 520DC60326F60FBA00FCFFFD /* SizingModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SizingModifiersTests.swift; sourceTree = ""; }; + 520DC60426F60FBA00FCFFFD /* NestedModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NestedModifiersTests.swift; sourceTree = ""; }; + 520DC60526F60FBA00FCFFFD /* PositioningModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PositioningModifiersTests.swift; sourceTree = ""; }; + 520DC60626F60FBA00FCFFFD /* TextInputModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextInputModifiersTests.swift; sourceTree = ""; }; + 520DC60726F60FBA00FCFFFD /* CustomStyleModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomStyleModifiersTests.swift; sourceTree = ""; }; + 520DC60826F60FBA00FCFFFD /* TransitiveModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitiveModifiersTests.swift; sourceTree = ""; }; + 520DC60926F60FBA00FCFFFD /* AccessibilityModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityModifiersTests.swift; sourceTree = ""; }; + 520DC60A26F60FBA00FCFFFD /* AnimationModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationModifiersTests.swift; sourceTree = ""; }; + 520DC60B26F60FBA00FCFFFD /* VisualEffectModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisualEffectModifiersTests.swift; sourceTree = ""; }; + 520DC60C26F60FBA00FCFFFD /* EnvironmentModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentModifiersTests.swift; sourceTree = ""; }; + 520DC60D26F60FBA00FCFFFD /* ViewPaddingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewPaddingTests.swift; sourceTree = ""; }; + 520DC60E26F60FBA00FCFFFD /* TransformingModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformingModifiersTests.swift; sourceTree = ""; }; + 520DC71226F60FF400FCFFFD /* watchOS-2-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "watchOS-2-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 529300FF26EFC96600012E90 /* watchOS-App-2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "watchOS-App-2.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5293010526EFC96600012E90 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; + 5293010D26EFC96700012E90 /* watchOS-Ext-2.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "watchOS-Ext-2.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5293011426EFC96700012E90 /* watchOSApp-2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "watchOSApp-2.swift"; sourceTree = ""; }; + 5293011A26EFC96800012E90 /* Ext-2-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Ext-2-Info.plist"; sourceTree = ""; }; + 52CA2F1026ECC56600BFD568 /* ViewInspector */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ViewInspector; path = ..; sourceTree = ""; }; + 52CA301526ECD64400BFD568 /* watchOSApp+Testable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "watchOSApp+Testable.swift"; sourceTree = ""; }; + 52E3258E26C72E7800CCE47E /* watchOS-App-1.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "watchOS-App-1.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 52E3259526C72E7900CCE47E /* App-1-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "App-1-Info.plist"; sourceTree = ""; }; + 52E3259A26C72E7900CCE47E /* watchOS-Ext-1.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "watchOS-Ext-1.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 52E325A126C72E7900CCE47E /* watchOSApp-1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "watchOSApp-1.swift"; sourceTree = ""; }; + 52E325AA26C72E7900CCE47E /* Ext-1-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Ext-1-Info.plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 520213A126ECB45D00E94C6E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 52CA301426ECC5D200BFD568 /* ViewInspector in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 520DC70B26F60FF400FCFFFD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 520DC70C26F60FF400FCFFFD /* ViewInspector in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5293010A26EFC96700012E90 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52E3259726C72E7900CCE47E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 520DC58926F60FA600FCFFFD /* watchOS-Tests */ = { + isa = PBXGroup; + children = ( + 520DC58A26F60FBA00FCFFFD /* ViewInspectorTests */, + ); + path = "watchOS-Tests"; + sourceTree = ""; + }; + 520DC58A26F60FBA00FCFFFD /* ViewInspectorTests */ = { + isa = PBXGroup; + children = ( + 520DC58B26F60FBA00FCFFFD /* LazyGroupTests.swift */, + 520DC58C26F60FBA00FCFFFD /* ViewHostingTests.swift */, + 520DC58D26F60FBA00FCFFFD /* InspectionEmissaryTests.swift */, + 520DC58E26F60FBA00FCFFFD /* BaseTypesTests.swift */, + 520DC58F26F60FBA00FCFFFD /* ViewSearchTests.swift */, + 520DC59026F60FBA00FCFFFD /* Gestures */, + 520DC5A926F60FBA00FCFFFD /* TestResources */, + 520DC5AE26F60FBA00FCFFFD /* InspectableViewTests.swift */, + 520DC5AF26F60FBA00FCFFFD /* InspectorTests.swift */, + 520DC5B026F60FBA00FCFFFD /* SwiftUI */, + 520DC5FE26F60FBA00FCFFFD /* ViewModifiers */, + ); + name = ViewInspectorTests; + path = ../../Tests/ViewInspectorTests; + sourceTree = ""; + }; + 520DC59026F60FBA00FCFFFD /* Gestures */ = { + isa = PBXGroup; + children = ( + 520DC59126F60FBA00FCFFFD /* GestureModifiers */, + 520DC59726F60FBA00FCFFFD /* ComposedGestureExampleTests.swift */, + 520DC59826F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift */, + 520DC59926F60FBA00FCFFFD /* RotationGestureTests.swift */, + 520DC59A26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift */, + 520DC59B26F60FBA00FCFFFD /* TapGestureTests.swift */, + 520DC59C26F60FBA00FCFFFD /* ExclusiveGestureTests.swift */, + 520DC59D26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift */, + 520DC59E26F60FBA00FCFFFD /* SequenceGestureTests.swift */, + 520DC59F26F60FBA00FCFFFD /* GestureExampleTests.swift */, + 520DC5A026F60FBA00FCFFFD /* DragGestureTests.swift */, + 520DC5A126F60FBA00FCFFFD /* CommonComposedGestureTests.swift */, + 520DC5A226F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift */, + 520DC5A326F60FBA00FCFFFD /* SimultaneousGestureTests.swift */, + 520DC5A426F60FBA00FCFFFD /* CommonComposedGestureEndedTests.swift */, + 520DC5A526F60FBA00FCFFFD /* CommonComposedGestureChangedTests.swift */, + 520DC5A626F60FBA00FCFFFD /* LongPressGestureTests.swift */, + 520DC5A726F60FBA00FCFFFD /* CommonGestureTests.swift */, + 520DC5A826F60FBA00FCFFFD /* MagnificationGestureTests.swift */, + ); + path = Gestures; + sourceTree = ""; + }; + 520DC59126F60FBA00FCFFFD /* GestureModifiers */ = { + isa = PBXGroup; + children = ( + 520DC59226F60FBA00FCFFFD /* HitTestingTests.swift */, + 520DC59326F60FBA00FCFFFD /* GestureModifierTests.swift */, + 520DC59426F60FBA00FCFFFD /* GestureActionTests.swift */, + 520DC59526F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift */, + 520DC59626F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift */, + ); + path = GestureModifiers; + sourceTree = ""; + }; + 520DC5A926F60FBA00FCFFFD /* TestResources */ = { + isa = PBXGroup; + children = ( + 520DC5AA26F60FBA00FCFFFD /* Test.strings */, + ); + path = TestResources; + sourceTree = ""; + }; + 520DC5B026F60FBA00FCFFFD /* SwiftUI */ = { + isa = PBXGroup; + children = ( + 520DC5B126F60FBA00FCFFFD /* ZStackTests.swift */, + 520DC5B226F60FBA00FCFFFD /* OptionalViewTests.swift */, + 520DC5B326F60FBA00FCFFFD /* MenuButtonTests.swift */, + 520DC5B426F60FBA00FCFFFD /* TouchBarTests.swift */, + 520DC5B526F60FBA00FCFFFD /* GroupBoxTests.swift */, + 520DC5B626F60FBA00FCFFFD /* SheetTests.swift */, + 520DC5B726F60FBA00FCFFFD /* RadialGradientTests.swift */, + 520DC5B826F60FBA00FCFFFD /* ScrollViewReaderTests.swift */, + 520DC5B926F60FBA00FCFFFD /* MapTests.swift */, + 520DC5BA26F60FBA00FCFFFD /* SubscriptionViewTests.swift */, + 520DC5BB26F60FBA00FCFFFD /* MenuTests.swift */, + 520DC5BC26F60FBA00FCFFFD /* LabelTests.swift */, + 520DC5BD26F60FBA00FCFFFD /* TextTests.swift */, + 520DC5BE26F60FBA00FCFFFD /* OpaqueViewTests.swift */, + 520DC5BF26F60FBA00FCFFFD /* HStackTests.swift */, + 520DC5C026F60FBA00FCFFFD /* TreeViewTests.swift */, + 520DC5C126F60FBA00FCFFFD /* EquatableViewTests.swift */, + 520DC5C226F60FBA00FCFFFD /* LinearGradientTests.swift */, + 520DC5C326F60FBA00FCFFFD /* ToolbarTests.swift */, + 520DC5C426F60FBA00FCFFFD /* DisclosureGroupTests.swift */, + 520DC5C526F60FBA00FCFFFD /* GeometryReaderTests.swift */, + 520DC5C626F60FBA00FCFFFD /* ProgressViewTests.swift */, + 520DC5C726F60FBA00FCFFFD /* EnvironmentObjectInjectionTests.swift */, + 520DC5C826F60FBA00FCFFFD /* TabViewTests.swift */, + 520DC5C926F60FBA00FCFFFD /* PopoverTests.swift */, + 520DC5CA26F60FBA00FCFFFD /* EmptyViewTests.swift */, + 520DC5CB26F60FBA00FCFFFD /* CustomViewModifierTests.swift */, + 520DC5CC26F60FBA00FCFFFD /* ImageTests.swift */, + 520DC5CD26F60FBA00FCFFFD /* EnvironmentReaderViewTests.swift */, + 520DC5CE26F60FBA00FCFFFD /* StepperTests.swift */, + 520DC5CF26F60FBA00FCFFFD /* FormTests.swift */, + 520DC5D026F60FBA00FCFFFD /* TupleViewTests.swift */, + 520DC5D126F60FBA00FCFFFD /* ConfirmationDialogTests.swift */, + 520DC5D226F60FBA00FCFFFD /* LazyVGridTests.swift */, + 520DC5D326F60FBA00FCFFFD /* LazyHStackTests.swift */, + 520DC5D426F60FBA00FCFFFD /* AlertTests.swift */, + 520DC5D526F60FBA00FCFFFD /* TextAttributesTests.swift */, + 520DC5D626F60FBA00FCFFFD /* PasteButtonTests.swift */, + 520DC5D726F60FBA00FCFFFD /* DividerTests.swift */, + 520DC5D826F60FBA00FCFFFD /* ForEachTests.swift */, + 520DC5D926F60FBA00FCFFFD /* ActionSheetTests.swift */, + 520DC5DA26F60FBA00FCFFFD /* DelayedPreferenceViewTests.swift */, + 520DC5DB26F60FBA00FCFFFD /* ButtonTests.swift */, + 520DC5DC26F60FBA00FCFFFD /* LinkTests.swift */, + 520DC5DD26F60FBA00FCFFFD /* SliderTests.swift */, + 520DC5DE26F60FBA00FCFFFD /* CustomViewTests.swift */, + 520DC5DF26F60FBA00FCFFFD /* ColorTests.swift */, + 520DC5E026F60FBA00FCFFFD /* TextEditorTests.swift */, + 520DC5E126F60FBA00FCFFFD /* MapAnnotationTests.swift */, + 520DC5E226F60FBA00FCFFFD /* AngularGradientTests.swift */, + 520DC5E326F60FBA00FCFFFD /* ConditionalContentTests.swift */, + 520DC5E426F60FBA00FCFFFD /* VSplitViewTests.swift */, + 520DC5E526F60FBA00FCFFFD /* NavigationViewTests.swift */, + 520DC5E626F60FBA00FCFFFD /* EditButtonTests.swift */, + 520DC5E726F60FBA00FCFFFD /* IDViewTests.swift */, + 520DC5E826F60FBA00FCFFFD /* CustomViewBuilderTests.swift */, + 520DC5E926F60FBA00FCFFFD /* TextFieldTests.swift */, + 520DC5EA26F60FBA00FCFFFD /* FullScreenCoverTests.swift */, + 520DC5EB26F60FBA00FCFFFD /* NavigationLinkTests.swift */, + 520DC5EC26F60FBA00FCFFFD /* VStackTests.swift */, + 520DC5ED26F60FBA00FCFFFD /* PickerTests.swift */, + 520DC5EE26F60FBA00FCFFFD /* GroupTests.swift */, + 520DC5EF26F60FBA00FCFFFD /* SectionTests.swift */, + 520DC5F026F60FBA00FCFFFD /* ToggleTests.swift */, + 520DC5F126F60FBA00FCFFFD /* SpacerTests.swift */, + 520DC5F226F60FBA00FCFFFD /* DatePickerTests.swift */, + 520DC5F326F60FBA00FCFFFD /* SafeAreaInsetTests.swift */, + 520DC5F426F60FBA00FCFFFD /* ColorPickerTests.swift */, + 520DC5F526F60FBA00FCFFFD /* OutlineGroupTests.swift */, + 520DC5F626F60FBA00FCFFFD /* ScrollViewTests.swift */, + 520DC5F726F60FBA00FCFFFD /* SecureFieldTests.swift */, + 520DC5F826F60FBA00FCFFFD /* LazyHGridTests.swift */, + 520DC5F926F60FBA00FCFFFD /* ShapeTests.swift */, + 520DC5FA26F60FBA00FCFFFD /* ListTests.swift */, + 520DC5FB26F60FBA00FCFFFD /* HSplitViewTests.swift */, + 520DC5FC26F60FBA00FCFFFD /* AnyViewTests.swift */, + 520DC5FD26F60FBA00FCFFFD /* LazyVStackTests.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; + 520DC5FE26F60FBA00FCFFFD /* ViewModifiers */ = { + isa = PBXGroup; + children = ( + 520DC5FF26F60FBA00FCFFFD /* ConfigurationModifiersTests.swift */, + 520DC60026F60FBA00FCFFFD /* PresentationModifiersTests.swift */, + 520DC60126F60FBA00FCFFFD /* InteractionModifiersTests.swift */, + 520DC60226F60FBA00FCFFFD /* EventsModifiersTests.swift */, + 520DC60326F60FBA00FCFFFD /* SizingModifiersTests.swift */, + 520DC60426F60FBA00FCFFFD /* NestedModifiersTests.swift */, + 520DC60526F60FBA00FCFFFD /* PositioningModifiersTests.swift */, + 520DC60626F60FBA00FCFFFD /* TextInputModifiersTests.swift */, + 520DC60726F60FBA00FCFFFD /* CustomStyleModifiersTests.swift */, + 520DC60826F60FBA00FCFFFD /* TransitiveModifiersTests.swift */, + 520DC60926F60FBA00FCFFFD /* AccessibilityModifiersTests.swift */, + 520DC60A26F60FBA00FCFFFD /* AnimationModifiersTests.swift */, + 520DC60B26F60FBA00FCFFFD /* VisualEffectModifiersTests.swift */, + 520DC60C26F60FBA00FCFFFD /* EnvironmentModifiersTests.swift */, + 520DC60D26F60FBA00FCFFFD /* ViewPaddingTests.swift */, + 520DC60E26F60FBA00FCFFFD /* TransformingModifiersTests.swift */, + ); + path = ViewModifiers; + sourceTree = ""; + }; + 52CA2F0F26ECC56600BFD568 /* Packages */ = { + isa = PBXGroup; + children = ( + 52CA2F1026ECC56600BFD568 /* ViewInspector */, + ); + name = Packages; + sourceTree = ""; + }; + 52CA301226ECC5D200BFD568 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 52E3258326C72E7800CCE47E = { + isa = PBXGroup; + children = ( + 52CA2F0F26ECC56600BFD568 /* Packages */, + 52E3259226C72E7800CCE47E /* watchOS-App */, + 52E3259E26C72E7900CCE47E /* watchOS-Ext */, + 520DC58926F60FA600FCFFFD /* watchOS-Tests */, + 52E3258B26C72E7800CCE47E /* Products */, + 52CA301226ECC5D200BFD568 /* Frameworks */, + ); + sourceTree = ""; + }; + 52E3258B26C72E7800CCE47E /* Products */ = { + isa = PBXGroup; + children = ( + 52E3258E26C72E7800CCE47E /* watchOS-App-1.app */, + 52E3259A26C72E7900CCE47E /* watchOS-Ext-1.appex */, + 520213A426ECB45D00E94C6E /* watchOS-1-Tests.xctest */, + 529300FF26EFC96600012E90 /* watchOS-App-2.app */, + 5293010D26EFC96700012E90 /* watchOS-Ext-2.appex */, + 520DC71226F60FF400FCFFFD /* watchOS-2-Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 52E3259226C72E7800CCE47E /* watchOS-App */ = { + isa = PBXGroup; + children = ( + 52E3259526C72E7900CCE47E /* App-1-Info.plist */, + 5293010426EFC96600012E90 /* Interface.storyboard */, + ); + path = "watchOS-App"; + sourceTree = ""; + }; + 52E3259E26C72E7900CCE47E /* watchOS-Ext */ = { + isa = PBXGroup; + children = ( + 52E325A126C72E7900CCE47E /* watchOSApp-1.swift */, + 5293011426EFC96700012E90 /* watchOSApp-2.swift */, + 52CA301526ECD64400BFD568 /* watchOSApp+Testable.swift */, + 52E325AA26C72E7900CCE47E /* Ext-1-Info.plist */, + 5293011A26EFC96800012E90 /* Ext-2-Info.plist */, + ); + path = "watchOS-Ext"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 520213A326ECB45D00E94C6E /* watchOS-1-Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 520213AC26ECB45D00E94C6E /* Build configuration list for PBXNativeTarget "watchOS-1-Tests" */; + buildPhases = ( + 520213A026ECB45D00E94C6E /* Sources */, + 520213A126ECB45D00E94C6E /* Frameworks */, + 520213A226ECB45D00E94C6E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 520213A926ECB45D00E94C6E /* PBXTargetDependency */, + ); + name = "watchOS-1-Tests"; + packageProductDependencies = ( + 52CA301326ECC5D200BFD568 /* ViewInspector */, + ); + productName = "watchOS-Tests"; + productReference = 520213A426ECB45D00E94C6E /* watchOS-1-Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 520DC68B26F60FF400FCFFFD /* watchOS-2-Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 520DC70F26F60FF400FCFFFD /* Build configuration list for PBXNativeTarget "watchOS-2-Tests" */; + buildPhases = ( + 520DC68F26F60FF400FCFFFD /* Sources */, + 520DC70B26F60FF400FCFFFD /* Frameworks */, + 520DC70D26F60FF400FCFFFD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 520DC68C26F60FF400FCFFFD /* PBXTargetDependency */, + 520DC71426F6103300FCFFFD /* PBXTargetDependency */, + ); + name = "watchOS-2-Tests"; + packageProductDependencies = ( + 520DC68E26F60FF400FCFFFD /* ViewInspector */, + ); + productName = "watchOS-Tests"; + productReference = 520DC71226F60FF400FCFFFD /* watchOS-2-Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 529300FE26EFC96600012E90 /* watchOS-App-2 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5293011F26EFC96800012E90 /* Build configuration list for PBXNativeTarget "watchOS-App-2" */; + buildPhases = ( + 529300FD26EFC96600012E90 /* Resources */, + 5293011E26EFC96800012E90 /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 5293011026EFC96700012E90 /* PBXTargetDependency */, + ); + name = "watchOS-App-2"; + productName = "watchOSApp-2 WatchKit App"; + productReference = 529300FF26EFC96600012E90 /* watchOS-App-2.app */; + productType = "com.apple.product-type.application.watchapp2"; + }; + 5293010C26EFC96700012E90 /* watchOS-Ext-2 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5293011B26EFC96800012E90 /* Build configuration list for PBXNativeTarget "watchOS-Ext-2" */; + buildPhases = ( + 5293010926EFC96700012E90 /* Sources */, + 5293010A26EFC96700012E90 /* Frameworks */, + 5293010B26EFC96700012E90 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "watchOS-Ext-2"; + productName = "watchOSApp-2 WatchKit Extension"; + productReference = 5293010D26EFC96700012E90 /* watchOS-Ext-2.appex */; + productType = "com.apple.product-type.watchkit2-extension"; + }; + 52E3258D26C72E7800CCE47E /* watchOS-App-1 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 52E325B126C72E7900CCE47E /* Build configuration list for PBXNativeTarget "watchOS-App-1" */; + buildPhases = ( + 52E3258C26C72E7800CCE47E /* Resources */, + 52E325B026C72E7900CCE47E /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 52E3259D26C72E7900CCE47E /* PBXTargetDependency */, + ); + name = "watchOS-App-1"; + productName = "watchOS WatchKit App"; + productReference = 52E3258E26C72E7800CCE47E /* watchOS-App-1.app */; + productType = "com.apple.product-type.application.watchapp2"; + }; + 52E3259926C72E7900CCE47E /* watchOS-Ext-1 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 52E325AD26C72E7900CCE47E /* Build configuration list for PBXNativeTarget "watchOS-Ext-1" */; + buildPhases = ( + 52E3259626C72E7900CCE47E /* Sources */, + 52E3259726C72E7900CCE47E /* Frameworks */, + 52E3259826C72E7900CCE47E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "watchOS-Ext-1"; + productName = "watchOS WatchKit Extension"; + productReference = 52E3259A26C72E7900CCE47E /* watchOS-Ext-1.appex */; + productType = "com.apple.product-type.watchkit2-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 52E3258426C72E7800CCE47E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1300; + LastUpgradeCheck = 1300; + TargetAttributes = { + 520213A326ECB45D00E94C6E = { + CreatedOnToolsVersion = 13.0; + TestTargetID = 52E3259926C72E7900CCE47E; + }; + 520DC68B26F60FF400FCFFFD = { + TestTargetID = 5293010C26EFC96700012E90; + }; + 529300FE26EFC96600012E90 = { + CreatedOnToolsVersion = 13.0; + }; + 5293010C26EFC96700012E90 = { + CreatedOnToolsVersion = 13.0; + }; + 52E3258D26C72E7800CCE47E = { + CreatedOnToolsVersion = 12.5.1; + }; + 52E3259926C72E7900CCE47E = { + CreatedOnToolsVersion = 12.5.1; + }; + }; + }; + buildConfigurationList = 52E3258726C72E7800CCE47E /* Build configuration list for PBXProject "watchOS" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + "en-AU", + ru, + ); + mainGroup = 52E3258326C72E7800CCE47E; + productRefGroup = 52E3258B26C72E7800CCE47E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 52E3258D26C72E7800CCE47E /* watchOS-App-1 */, + 529300FE26EFC96600012E90 /* watchOS-App-2 */, + 52E3259926C72E7900CCE47E /* watchOS-Ext-1 */, + 5293010C26EFC96700012E90 /* watchOS-Ext-2 */, + 520213A326ECB45D00E94C6E /* watchOS-1-Tests */, + 520DC68B26F60FF400FCFFFD /* watchOS-2-Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 520213A226ECB45D00E94C6E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 520DC62B26F60FBB00FCFFFD /* Test.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 520DC70D26F60FF400FCFFFD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 520DC70E26F60FF400FCFFFD /* Test.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 529300FD26EFC96600012E90 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5293010626EFC96600012E90 /* Interface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5293010B26EFC96700012E90 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52E3258C26C72E7800CCE47E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52E3259826C72E7900CCE47E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 520213A026ECB45D00E94C6E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 520DC65126F60FBB00FCFFFD /* AlertTests.swift in Sources */, + 520DC62C26F60FBB00FCFFFD /* InspectableViewTests.swift in Sources */, + 520DC61E26F60FBA00FCFFFD /* ExclusiveGestureTests.swift in Sources */, + 520DC63026F60FBB00FCFFFD /* MenuButtonTests.swift in Sources */, + 520DC64426F60FBB00FCFFFD /* EnvironmentObjectInjectionTests.swift in Sources */, + 520DC67A26F60FBB00FCFFFD /* LazyVStackTests.swift in Sources */, + 520DC65E26F60FBB00FCFFFD /* MapAnnotationTests.swift in Sources */, + 520DC63826F60FBB00FCFFFD /* MenuTests.swift in Sources */, + 520DC67C26F60FBB00FCFFFD /* PresentationModifiersTests.swift in Sources */, + 520DC65A26F60FBB00FCFFFD /* SliderTests.swift in Sources */, + 520DC64626F60FBB00FCFFFD /* PopoverTests.swift in Sources */, + 520DC65626F60FBB00FCFFFD /* ActionSheetTests.swift in Sources */, + 520DC66E26F60FBB00FCFFFD /* SpacerTests.swift in Sources */, + 520DC63426F60FBB00FCFFFD /* RadialGradientTests.swift in Sources */, + 520DC66626F60FBB00FCFFFD /* TextFieldTests.swift in Sources */, + 520DC65926F60FBB00FCFFFD /* LinkTests.swift in Sources */, + 520DC67E26F60FBB00FCFFFD /* EventsModifiersTests.swift in Sources */, + 520DC64B26F60FBB00FCFFFD /* StepperTests.swift in Sources */, + 520DC64E26F60FBB00FCFFFD /* ConfirmationDialogTests.swift in Sources */, + 520DC67826F60FBB00FCFFFD /* HSplitViewTests.swift in Sources */, + 520DC65B26F60FBB00FCFFFD /* CustomViewTests.swift in Sources */, + 520DC67726F60FBB00FCFFFD /* ListTests.swift in Sources */, + 520DC61426F60FBA00FCFFFD /* HitTestingTests.swift in Sources */, + 520DC66C26F60FBB00FCFFFD /* SectionTests.swift in Sources */, + 520DC66126F60FBB00FCFFFD /* VSplitViewTests.swift in Sources */, + 520DC68826F60FBB00FCFFFD /* EnvironmentModifiersTests.swift in Sources */, + 520DC67426F60FBB00FCFFFD /* SecureFieldTests.swift in Sources */, + 520DC68726F60FBB00FCFFFD /* VisualEffectModifiersTests.swift in Sources */, + 520DC67926F60FBB00FCFFFD /* AnyViewTests.swift in Sources */, + 520DC65F26F60FBB00FCFFFD /* AngularGradientTests.swift in Sources */, + 520DC66A26F60FBB00FCFFFD /* PickerTests.swift in Sources */, + 520DC62526F60FBB00FCFFFD /* SimultaneousGestureTests.swift in Sources */, + 520DC67F26F60FBB00FCFFFD /* SizingModifiersTests.swift in Sources */, + 520DC66526F60FBB00FCFFFD /* CustomViewBuilderTests.swift in Sources */, + 520DC62E26F60FBB00FCFFFD /* ZStackTests.swift in Sources */, + 520DC62726F60FBB00FCFFFD /* CommonComposedGestureChangedTests.swift in Sources */, + 520DC66326F60FBB00FCFFFD /* EditButtonTests.swift in Sources */, + 520DC63B26F60FBB00FCFFFD /* OpaqueViewTests.swift in Sources */, + 520DC64726F60FBB00FCFFFD /* EmptyViewTests.swift in Sources */, + 520DC62F26F60FBB00FCFFFD /* OptionalViewTests.swift in Sources */, + 520DC67B26F60FBB00FCFFFD /* ConfigurationModifiersTests.swift in Sources */, + 520DC67D26F60FBB00FCFFFD /* InteractionModifiersTests.swift in Sources */, + 520DC68126F60FBB00FCFFFD /* PositioningModifiersTests.swift in Sources */, + 520DC68026F60FBB00FCFFFD /* NestedModifiersTests.swift in Sources */, + 520DC68426F60FBB00FCFFFD /* TransitiveModifiersTests.swift in Sources */, + 520DC68626F60FBB00FCFFFD /* AnimationModifiersTests.swift in Sources */, + 520DC66426F60FBB00FCFFFD /* IDViewTests.swift in Sources */, + 520DC63726F60FBB00FCFFFD /* SubscriptionViewTests.swift in Sources */, + 520DC61A26F60FBA00FCFFFD /* SequenceGestureChildrenTests.swift in Sources */, + 520DC65D26F60FBB00FCFFFD /* TextEditorTests.swift in Sources */, + 520DC61826F60FBA00FCFFFD /* SimultaneousGestureModifierTests.swift in Sources */, + 520DC61726F60FBA00FCFFFD /* HighPriorityGestureModifierTests.swift in Sources */, + 520DC67626F60FBB00FCFFFD /* ShapeTests.swift in Sources */, + 520DC64026F60FBB00FCFFFD /* ToolbarTests.swift in Sources */, + 520DC63C26F60FBB00FCFFFD /* HStackTests.swift in Sources */, + 520DC63126F60FBB00FCFFFD /* TouchBarTests.swift in Sources */, + 520DC64826F60FBB00FCFFFD /* CustomViewModifierTests.swift in Sources */, + 520DC68226F60FBB00FCFFFD /* TextInputModifiersTests.swift in Sources */, + 520DC62126F60FBA00FCFFFD /* GestureExampleTests.swift in Sources */, + 520DC61F26F60FBA00FCFFFD /* SimultaneousGestureChildrenTests.swift in Sources */, + 520DC61526F60FBA00FCFFFD /* GestureModifierTests.swift in Sources */, + 520DC64926F60FBB00FCFFFD /* ImageTests.swift in Sources */, + 520DC65C26F60FBB00FCFFFD /* ColorTests.swift in Sources */, + 520DC65826F60FBB00FCFFFD /* ButtonTests.swift in Sources */, + 520DC63326F60FBB00FCFFFD /* SheetTests.swift in Sources */, + 520DC62426F60FBA00FCFFFD /* CommonComposedGestureUpdatingTests.swift in Sources */, + 520DC64A26F60FBB00FCFFFD /* EnvironmentReaderViewTests.swift in Sources */, + 520DC67126F60FBB00FCFFFD /* ColorPickerTests.swift in Sources */, + 520DC67226F60FBB00FCFFFD /* OutlineGroupTests.swift in Sources */, + 520DC61126F60FBA00FCFFFD /* InspectionEmissaryTests.swift in Sources */, + 520DC66D26F60FBB00FCFFFD /* ToggleTests.swift in Sources */, + 520DC62226F60FBA00FCFFFD /* DragGestureTests.swift in Sources */, + 520DC66026F60FBB00FCFFFD /* ConditionalContentTests.swift in Sources */, + 520DC68A26F60FBB00FCFFFD /* TransformingModifiersTests.swift in Sources */, + 520DC64326F60FBB00FCFFFD /* ProgressViewTests.swift in Sources */, + 520DC66B26F60FBB00FCFFFD /* GroupTests.swift in Sources */, + 520DC64D26F60FBB00FCFFFD /* TupleViewTests.swift in Sources */, + 520DC68526F60FBB00FCFFFD /* AccessibilityModifiersTests.swift in Sources */, + 520DC63526F60FBB00FCFFFD /* ScrollViewReaderTests.swift in Sources */, + 520DC62826F60FBB00FCFFFD /* LongPressGestureTests.swift in Sources */, + 520DC67026F60FBB00FCFFFD /* SafeAreaInsetTests.swift in Sources */, + 520DC68926F60FBB00FCFFFD /* ViewPaddingTests.swift in Sources */, + 520DC68326F60FBB00FCFFFD /* CustomStyleModifiersTests.swift in Sources */, + 520DC62D26F60FBB00FCFFFD /* InspectorTests.swift in Sources */, + 520DC63A26F60FBB00FCFFFD /* TextTests.swift in Sources */, + 520DC61D26F60FBA00FCFFFD /* TapGestureTests.swift in Sources */, + 520DC62626F60FBB00FCFFFD /* CommonComposedGestureEndedTests.swift in Sources */, + 520DC62926F60FBB00FCFFFD /* CommonGestureTests.swift in Sources */, + 520DC66F26F60FBB00FCFFFD /* DatePickerTests.swift in Sources */, + 520DC66726F60FBB00FCFFFD /* FullScreenCoverTests.swift in Sources */, + 520DC63626F60FBB00FCFFFD /* MapTests.swift in Sources */, + 520DC63F26F60FBB00FCFFFD /* LinearGradientTests.swift in Sources */, + 520DC64C26F60FBB00FCFFFD /* FormTests.swift in Sources */, + 520DC63E26F60FBB00FCFFFD /* EquatableViewTests.swift in Sources */, + 520DC65726F60FBB00FCFFFD /* DelayedPreferenceViewTests.swift in Sources */, + 520DC67526F60FBB00FCFFFD /* LazyHGridTests.swift in Sources */, + 520DC61626F60FBA00FCFFFD /* GestureActionTests.swift in Sources */, + 520DC61926F60FBA00FCFFFD /* ComposedGestureExampleTests.swift in Sources */, + 520DC65326F60FBB00FCFFFD /* PasteButtonTests.swift in Sources */, + 520DC65526F60FBB00FCFFFD /* ForEachTests.swift in Sources */, + 520DC66826F60FBB00FCFFFD /* NavigationLinkTests.swift in Sources */, + 520DC65426F60FBB00FCFFFD /* DividerTests.swift in Sources */, + 520DC60F26F60FBA00FCFFFD /* LazyGroupTests.swift in Sources */, + 520DC64526F60FBB00FCFFFD /* TabViewTests.swift in Sources */, + 520DC61226F60FBA00FCFFFD /* BaseTypesTests.swift in Sources */, + 520DC61B26F60FBA00FCFFFD /* RotationGestureTests.swift in Sources */, + 520DC66226F60FBB00FCFFFD /* NavigationViewTests.swift in Sources */, + 520DC61C26F60FBA00FCFFFD /* ExclusiveGestureChildrenTests.swift in Sources */, + 520DC61026F60FBA00FCFFFD /* ViewHostingTests.swift in Sources */, + 520DC63226F60FBB00FCFFFD /* GroupBoxTests.swift in Sources */, + 520DC62326F60FBA00FCFFFD /* CommonComposedGestureTests.swift in Sources */, + 520DC63926F60FBB00FCFFFD /* LabelTests.swift in Sources */, + 520DC62A26F60FBB00FCFFFD /* MagnificationGestureTests.swift in Sources */, + 520DC66926F60FBB00FCFFFD /* VStackTests.swift in Sources */, + 520DC62026F60FBA00FCFFFD /* SequenceGestureTests.swift in Sources */, + 520DC65226F60FBB00FCFFFD /* TextAttributesTests.swift in Sources */, + 520DC67326F60FBB00FCFFFD /* ScrollViewTests.swift in Sources */, + 520DC63D26F60FBB00FCFFFD /* TreeViewTests.swift in Sources */, + 520DC65026F60FBB00FCFFFD /* LazyHStackTests.swift in Sources */, + 520DC64126F60FBB00FCFFFD /* DisclosureGroupTests.swift in Sources */, + 520DC61326F60FBA00FCFFFD /* ViewSearchTests.swift in Sources */, + 520DC64F26F60FBB00FCFFFD /* LazyVGridTests.swift in Sources */, + 520DC64226F60FBB00FCFFFD /* GeometryReaderTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 520DC68F26F60FF400FCFFFD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 520DC69026F60FF400FCFFFD /* AlertTests.swift in Sources */, + 520DC69126F60FF400FCFFFD /* InspectableViewTests.swift in Sources */, + 520DC69226F60FF400FCFFFD /* ExclusiveGestureTests.swift in Sources */, + 520DC69326F60FF400FCFFFD /* MenuButtonTests.swift in Sources */, + 520DC69426F60FF400FCFFFD /* EnvironmentObjectInjectionTests.swift in Sources */, + 520DC69526F60FF400FCFFFD /* LazyVStackTests.swift in Sources */, + 520DC69626F60FF400FCFFFD /* MapAnnotationTests.swift in Sources */, + 520DC69726F60FF400FCFFFD /* MenuTests.swift in Sources */, + 520DC69826F60FF400FCFFFD /* PresentationModifiersTests.swift in Sources */, + 520DC69926F60FF400FCFFFD /* SliderTests.swift in Sources */, + 520DC69A26F60FF400FCFFFD /* PopoverTests.swift in Sources */, + 520DC69B26F60FF400FCFFFD /* ActionSheetTests.swift in Sources */, + 520DC69C26F60FF400FCFFFD /* SpacerTests.swift in Sources */, + 520DC69D26F60FF400FCFFFD /* RadialGradientTests.swift in Sources */, + 520DC69E26F60FF400FCFFFD /* TextFieldTests.swift in Sources */, + 520DC69F26F60FF400FCFFFD /* LinkTests.swift in Sources */, + 520DC6A026F60FF400FCFFFD /* EventsModifiersTests.swift in Sources */, + 520DC6A126F60FF400FCFFFD /* StepperTests.swift in Sources */, + 520DC6A226F60FF400FCFFFD /* ConfirmationDialogTests.swift in Sources */, + 520DC6A326F60FF400FCFFFD /* HSplitViewTests.swift in Sources */, + 520DC6A426F60FF400FCFFFD /* CustomViewTests.swift in Sources */, + 520DC6A526F60FF400FCFFFD /* ListTests.swift in Sources */, + 520DC6A626F60FF400FCFFFD /* HitTestingTests.swift in Sources */, + 520DC6A726F60FF400FCFFFD /* SectionTests.swift in Sources */, + 520DC6A826F60FF400FCFFFD /* VSplitViewTests.swift in Sources */, + 520DC6A926F60FF400FCFFFD /* EnvironmentModifiersTests.swift in Sources */, + 520DC6AA26F60FF400FCFFFD /* SecureFieldTests.swift in Sources */, + 520DC6AB26F60FF400FCFFFD /* VisualEffectModifiersTests.swift in Sources */, + 520DC6AC26F60FF400FCFFFD /* AnyViewTests.swift in Sources */, + 520DC6AD26F60FF400FCFFFD /* AngularGradientTests.swift in Sources */, + 520DC6AE26F60FF400FCFFFD /* PickerTests.swift in Sources */, + 520DC6AF26F60FF400FCFFFD /* SimultaneousGestureTests.swift in Sources */, + 520DC6B026F60FF400FCFFFD /* SizingModifiersTests.swift in Sources */, + 520DC6B126F60FF400FCFFFD /* CustomViewBuilderTests.swift in Sources */, + 520DC6B226F60FF400FCFFFD /* ZStackTests.swift in Sources */, + 520DC6B326F60FF400FCFFFD /* CommonComposedGestureChangedTests.swift in Sources */, + 520DC6B426F60FF400FCFFFD /* EditButtonTests.swift in Sources */, + 520DC6B526F60FF400FCFFFD /* OpaqueViewTests.swift in Sources */, + 520DC6B626F60FF400FCFFFD /* EmptyViewTests.swift in Sources */, + 520DC6B726F60FF400FCFFFD /* OptionalViewTests.swift in Sources */, + 520DC6B826F60FF400FCFFFD /* ConfigurationModifiersTests.swift in Sources */, + 520DC6B926F60FF400FCFFFD /* InteractionModifiersTests.swift in Sources */, + 520DC6BA26F60FF400FCFFFD /* PositioningModifiersTests.swift in Sources */, + 520DC6BB26F60FF400FCFFFD /* NestedModifiersTests.swift in Sources */, + 520DC6BC26F60FF400FCFFFD /* TransitiveModifiersTests.swift in Sources */, + 520DC6BD26F60FF400FCFFFD /* AnimationModifiersTests.swift in Sources */, + 520DC6BE26F60FF400FCFFFD /* IDViewTests.swift in Sources */, + 520DC6BF26F60FF400FCFFFD /* SubscriptionViewTests.swift in Sources */, + 520DC6C026F60FF400FCFFFD /* SequenceGestureChildrenTests.swift in Sources */, + 520DC6C126F60FF400FCFFFD /* TextEditorTests.swift in Sources */, + 520DC6C226F60FF400FCFFFD /* SimultaneousGestureModifierTests.swift in Sources */, + 520DC6C326F60FF400FCFFFD /* HighPriorityGestureModifierTests.swift in Sources */, + 520DC6C426F60FF400FCFFFD /* ShapeTests.swift in Sources */, + 520DC6C526F60FF400FCFFFD /* ToolbarTests.swift in Sources */, + 520DC6C626F60FF400FCFFFD /* HStackTests.swift in Sources */, + 520DC6C726F60FF400FCFFFD /* TouchBarTests.swift in Sources */, + 520DC6C826F60FF400FCFFFD /* CustomViewModifierTests.swift in Sources */, + 520DC6C926F60FF400FCFFFD /* TextInputModifiersTests.swift in Sources */, + 520DC6CA26F60FF400FCFFFD /* GestureExampleTests.swift in Sources */, + 520DC6CB26F60FF400FCFFFD /* SimultaneousGestureChildrenTests.swift in Sources */, + 520DC6CC26F60FF400FCFFFD /* GestureModifierTests.swift in Sources */, + 520DC6CD26F60FF400FCFFFD /* ImageTests.swift in Sources */, + 520DC6CE26F60FF400FCFFFD /* ColorTests.swift in Sources */, + 520DC6CF26F60FF400FCFFFD /* ButtonTests.swift in Sources */, + 520DC6D026F60FF400FCFFFD /* SheetTests.swift in Sources */, + 520DC6D126F60FF400FCFFFD /* CommonComposedGestureUpdatingTests.swift in Sources */, + 520DC6D226F60FF400FCFFFD /* EnvironmentReaderViewTests.swift in Sources */, + 520DC6D326F60FF400FCFFFD /* ColorPickerTests.swift in Sources */, + 520DC6D426F60FF400FCFFFD /* OutlineGroupTests.swift in Sources */, + 520DC6D526F60FF400FCFFFD /* InspectionEmissaryTests.swift in Sources */, + 520DC6D626F60FF400FCFFFD /* ToggleTests.swift in Sources */, + 520DC6D726F60FF400FCFFFD /* DragGestureTests.swift in Sources */, + 520DC6D826F60FF400FCFFFD /* ConditionalContentTests.swift in Sources */, + 520DC6D926F60FF400FCFFFD /* TransformingModifiersTests.swift in Sources */, + 520DC6DA26F60FF400FCFFFD /* ProgressViewTests.swift in Sources */, + 520DC6DB26F60FF400FCFFFD /* GroupTests.swift in Sources */, + 520DC6DC26F60FF400FCFFFD /* TupleViewTests.swift in Sources */, + 520DC6DD26F60FF400FCFFFD /* AccessibilityModifiersTests.swift in Sources */, + 520DC6DE26F60FF400FCFFFD /* ScrollViewReaderTests.swift in Sources */, + 520DC6DF26F60FF400FCFFFD /* LongPressGestureTests.swift in Sources */, + 520DC6E026F60FF400FCFFFD /* SafeAreaInsetTests.swift in Sources */, + 520DC6E126F60FF400FCFFFD /* ViewPaddingTests.swift in Sources */, + 520DC6E226F60FF400FCFFFD /* CustomStyleModifiersTests.swift in Sources */, + 520DC6E326F60FF400FCFFFD /* InspectorTests.swift in Sources */, + 520DC6E426F60FF400FCFFFD /* TextTests.swift in Sources */, + 520DC6E526F60FF400FCFFFD /* TapGestureTests.swift in Sources */, + 520DC6E626F60FF400FCFFFD /* CommonComposedGestureEndedTests.swift in Sources */, + 520DC6E726F60FF400FCFFFD /* CommonGestureTests.swift in Sources */, + 520DC6E826F60FF400FCFFFD /* DatePickerTests.swift in Sources */, + 520DC6E926F60FF400FCFFFD /* FullScreenCoverTests.swift in Sources */, + 520DC6EA26F60FF400FCFFFD /* MapTests.swift in Sources */, + 520DC6EB26F60FF400FCFFFD /* LinearGradientTests.swift in Sources */, + 520DC6EC26F60FF400FCFFFD /* FormTests.swift in Sources */, + 520DC6ED26F60FF400FCFFFD /* EquatableViewTests.swift in Sources */, + 520DC6EE26F60FF400FCFFFD /* DelayedPreferenceViewTests.swift in Sources */, + 520DC6EF26F60FF400FCFFFD /* LazyHGridTests.swift in Sources */, + 520DC6F026F60FF400FCFFFD /* GestureActionTests.swift in Sources */, + 520DC6F126F60FF400FCFFFD /* ComposedGestureExampleTests.swift in Sources */, + 520DC6F226F60FF400FCFFFD /* PasteButtonTests.swift in Sources */, + 520DC6F326F60FF400FCFFFD /* ForEachTests.swift in Sources */, + 520DC6F426F60FF400FCFFFD /* NavigationLinkTests.swift in Sources */, + 520DC6F526F60FF400FCFFFD /* DividerTests.swift in Sources */, + 520DC6F626F60FF400FCFFFD /* LazyGroupTests.swift in Sources */, + 520DC6F726F60FF400FCFFFD /* TabViewTests.swift in Sources */, + 520DC6F826F60FF400FCFFFD /* BaseTypesTests.swift in Sources */, + 520DC6F926F60FF400FCFFFD /* RotationGestureTests.swift in Sources */, + 520DC6FA26F60FF400FCFFFD /* NavigationViewTests.swift in Sources */, + 520DC6FB26F60FF400FCFFFD /* ExclusiveGestureChildrenTests.swift in Sources */, + 520DC6FC26F60FF400FCFFFD /* ViewHostingTests.swift in Sources */, + 520DC6FD26F60FF400FCFFFD /* GroupBoxTests.swift in Sources */, + 520DC6FE26F60FF400FCFFFD /* CommonComposedGestureTests.swift in Sources */, + 520DC6FF26F60FF400FCFFFD /* LabelTests.swift in Sources */, + 520DC70026F60FF400FCFFFD /* MagnificationGestureTests.swift in Sources */, + 520DC70126F60FF400FCFFFD /* VStackTests.swift in Sources */, + 520DC70226F60FF400FCFFFD /* SequenceGestureTests.swift in Sources */, + 520DC70326F60FF400FCFFFD /* TextAttributesTests.swift in Sources */, + 520DC70426F60FF400FCFFFD /* ScrollViewTests.swift in Sources */, + 520DC70526F60FF400FCFFFD /* TreeViewTests.swift in Sources */, + 520DC70626F60FF400FCFFFD /* LazyHStackTests.swift in Sources */, + 520DC70726F60FF400FCFFFD /* DisclosureGroupTests.swift in Sources */, + 520DC70826F60FF400FCFFFD /* ViewSearchTests.swift in Sources */, + 520DC70926F60FF400FCFFFD /* LazyVGridTests.swift in Sources */, + 520DC70A26F60FF400FCFFFD /* GeometryReaderTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5293010926EFC96700012E90 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5293012726EFCBD300012E90 /* watchOSApp-2.swift in Sources */, + 520DC71526F6115D00FCFFFD /* watchOSApp+Testable.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52E3259626C72E7900CCE47E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52E325A226C72E7900CCE47E /* watchOSApp-1.swift in Sources */, + 52CA301626ECD64400BFD568 /* watchOSApp+Testable.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 520213A926ECB45D00E94C6E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 52E3259926C72E7900CCE47E /* watchOS-Ext-1 */; + targetProxy = 520213A826ECB45D00E94C6E /* PBXContainerItemProxy */; + }; + 520DC68C26F60FF400FCFFFD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 52E3259926C72E7900CCE47E /* watchOS-Ext-1 */; + targetProxy = 520DC68D26F60FF400FCFFFD /* PBXContainerItemProxy */; + }; + 520DC71426F6103300FCFFFD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5293010C26EFC96700012E90 /* watchOS-Ext-2 */; + targetProxy = 520DC71326F6103300FCFFFD /* PBXContainerItemProxy */; + }; + 5293011026EFC96700012E90 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5293010C26EFC96700012E90 /* watchOS-Ext-2 */; + targetProxy = 5293010F26EFC96700012E90 /* PBXContainerItemProxy */; + }; + 52E3259D26C72E7900CCE47E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 52E3259926C72E7900CCE47E /* watchOS-Ext-1 */; + targetProxy = 52E3259C26C72E7900CCE47E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 520DC5AA26F60FBA00FCFFFD /* Test.strings */ = { + isa = PBXVariantGroup; + children = ( + 520DC5AB26F60FBA00FCFFFD /* en */, + 520DC5AC26F60FBA00FCFFFD /* en-AU */, + 520DC5AD26F60FBA00FCFFFD /* ru */, + ); + name = Test.strings; + sourceTree = ""; + }; + 5293010426EFC96600012E90 /* Interface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5293010526EFC96600012E90 /* Base */, + ); + name = Interface.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 520213AA26ECB45D00E94C6E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.viewinspector.watchOS-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-Ext-1.appex/watchOS-Ext-1"; + }; + name = Debug; + }; + 520213AB26ECB45D00E94C6E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.viewinspector.watchOS-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-Ext-1.appex/watchOS-Ext-1"; + }; + name = Release; + }; + 520DC71026F60FF400FCFFFD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.viewinspector.watchOS-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-Ext-2.appex/watchOS-Ext-2"; + }; + name = Debug; + }; + 520DC71126F60FF400FCFFFD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.viewinspector.watchOS-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-Ext-2.appex/watchOS-Ext-2"; + }; + name = Release; + }; + 5293011C26EFC96800012E90 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "$(SRCROOT)/watchOS-Ext/Ext-2-Info.plist"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_WKExtensionDelegateClassName = watchOS_Ext_2.ExtensionDelegate; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS2.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + }; + name = Debug; + }; + 5293011D26EFC96800012E90 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "$(SRCROOT)/watchOS-Ext/Ext-2-Info.plist"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_WKExtensionDelegateClassName = watchOS_Ext_2.ExtensionDelegate; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS2.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + }; + name = Release; + }; + 5293012026EFC96800012E90 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IBSC_MODULE = watchOS_App_2; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS2.watchkitapp; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 5293012126EFC96800012E90 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IBSC_MODULE = watchOS_App_2; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS2.watchkitapp; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 52E325AB26C72E7900CCE47E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Debug; + }; + 52E325AC26C72E7900CCE47E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Release; + }; + 52E325AE26C72E7900CCE47E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "$(SRCROOT)/watchOS-Ext/Ext-1-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS1.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 52E325AF26C72E7900CCE47E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "$(SRCROOT)/watchOS-Ext/Ext-1-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS1.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 52E325B226C72E7900CCE47E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS1.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 52E325B326C72E7900CCE47E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.viewinspector.watchOS1.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 520213AC26ECB45D00E94C6E /* Build configuration list for PBXNativeTarget "watchOS-1-Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 520213AA26ECB45D00E94C6E /* Debug */, + 520213AB26ECB45D00E94C6E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 520DC70F26F60FF400FCFFFD /* Build configuration list for PBXNativeTarget "watchOS-2-Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 520DC71026F60FF400FCFFFD /* Debug */, + 520DC71126F60FF400FCFFFD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5293011B26EFC96800012E90 /* Build configuration list for PBXNativeTarget "watchOS-Ext-2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5293011C26EFC96800012E90 /* Debug */, + 5293011D26EFC96800012E90 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5293011F26EFC96800012E90 /* Build configuration list for PBXNativeTarget "watchOS-App-2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5293012026EFC96800012E90 /* Debug */, + 5293012126EFC96800012E90 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 52E3258726C72E7800CCE47E /* Build configuration list for PBXProject "watchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52E325AB26C72E7900CCE47E /* Debug */, + 52E325AC26C72E7900CCE47E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 52E325AD26C72E7900CCE47E /* Build configuration list for PBXNativeTarget "watchOS-Ext-1" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52E325AE26C72E7900CCE47E /* Debug */, + 52E325AF26C72E7900CCE47E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 52E325B126C72E7900CCE47E /* Build configuration list for PBXNativeTarget "watchOS-App-1" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52E325B226C72E7900CCE47E /* Debug */, + 52E325B326C72E7900CCE47E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 520DC68E26F60FF400FCFFFD /* ViewInspector */ = { + isa = XCSwiftPackageProductDependency; + productName = ViewInspector; + }; + 52CA301326ECC5D200BFD568 /* ViewInspector */ = { + isa = XCSwiftPackageProductDependency; + productName = ViewInspector; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 52E3258426C72E7800CCE47E /* Project object */; +} diff --git a/.watchOS/watchOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.watchOS/watchOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/.watchOS/watchOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ViewInspector.xcscheme b/.watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-1.xcscheme similarity index 60% rename from .swiftpm/xcode/xcshareddata/xcschemes/ViewInspector.xcscheme rename to .watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-1.xcscheme index 0cec304d..3dc144f8 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ViewInspector.xcscheme +++ b/.watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-1.xcscheme @@ -1,6 +1,6 @@ + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> + BlueprintIdentifier = "52E3258D26C72E7800CCE47E" + BuildableName = "watchOS-App-1.app" + BlueprintName = "watchOS-App-1" + ReferencedContainer = "container:watchOS.xcodeproj"> + buildForAnalyzing = "NO"> + BlueprintIdentifier = "520213A326ECB45D00E94C6E" + BuildableName = "watchOS-1-Tests.xctest" + BlueprintName = "watchOS-1-Tests" + ReferencedContainer = "container:watchOS.xcodeproj"> @@ -40,26 +40,16 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + BlueprintIdentifier = "520213A326ECB45D00E94C6E" + BuildableName = "watchOS-1-Tests.xctest" + BlueprintName = "watchOS-1-Tests" + ReferencedContainer = "container:watchOS.xcodeproj"> @@ -74,6 +64,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + + + + BlueprintIdentifier = "52E3258D26C72E7800CCE47E" + BuildableName = "watchOS-App-1.app" + BlueprintName = "watchOS-App-1" + ReferencedContainer = "container:watchOS.xcodeproj"> diff --git a/.watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-2.xcscheme b/.watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-2.xcscheme new file mode 100644 index 00000000..33c5de47 --- /dev/null +++ b/.watchOS/watchOS.xcodeproj/xcshareddata/xcschemes/watchOS-App-2.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index b9dd8548..047daed4 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,7 @@ let package = Package( name: "ViewInspector", defaultLocalization: "en", platforms: [ - .macOS(.v10_15), .iOS(.v11), .tvOS(.v13) + .macOS(.v10_15), .iOS(.v11), .tvOS(.v13), .watchOS(.v7) ], products: [ .library( diff --git a/README.md b/README.md index dce1d6f0..417930cf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20tvOS%20%7C%20macOS-lightgrey) [![Build Status](https://travis-ci.com/nalexn/ViewInspector.svg?branch=master)](https://travis-ci.com/nalexn/ViewInspector) [![codecov](https://codecov.io/gh/nalexn/ViewInspector/branch/master/graph/badge.svg)](https://codecov.io/gh/nalexn/ViewInspector) +![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey) [![Build Status](https://travis-ci.com/nalexn/ViewInspector.svg?branch=master)](https://travis-ci.com/nalexn/ViewInspector) [![codecov](https://codecov.io/gh/nalexn/ViewInspector/branch/master/graph/badge.svg)](https://codecov.io/gh/nalexn/ViewInspector) diff --git a/Sources/ViewInspector/BaseTypes.swift b/Sources/ViewInspector/BaseTypes.swift index f39440a7..2db51bb3 100644 --- a/Sources/ViewInspector/BaseTypes.swift +++ b/Sources/ViewInspector/BaseTypes.swift @@ -104,11 +104,17 @@ public extension KnownViewType { } static var isTransitive: Bool { false } static func inspectionCall(typeName: String) -> String { - let baseName = typePrefix.prefix(1).lowercased() + typePrefix.dropFirst() + let baseName = typePrefix.firstLetterLowercased return "\(baseName)(\(ViewType.indexPlaceholder))" } } +internal extension String { + var firstLetterLowercased: String { + prefix(1).lowercased() + dropFirst() + } +} + @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public struct ViewType { } @@ -281,11 +287,11 @@ extension InspectionError: CustomStringConvertible, LocalizedError { // MARK: - BinaryEquatable @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal protocol BinaryEquatable: Equatable { } +public protocol BinaryEquatable: Equatable { } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) extension BinaryEquatable { - static func == (lhs: Self, rhs: Self) -> Bool { + public static func == (lhs: Self, rhs: Self) -> Bool { withUnsafeBytes(of: lhs) { lhsBytes -> Bool in withUnsafeBytes(of: rhs) { rhsBytes -> Bool in lhsBytes.elementsEqual(rhsBytes) @@ -293,83 +299,3 @@ extension BinaryEquatable { } } } - -// MARK: - EnvironmentObject injection - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal extension Inspectable { - var missingEnvironmentObjects: [String] { - let prefix = "SwiftUI.EnvironmentObject<" - let mirror = Mirror(reflecting: self) - return mirror.children.compactMap { - let fullName = Inspector.typeName(value: $0.value, namespaced: true) - guard fullName.hasPrefix(prefix), - (try? Inspector.attribute(path: "_store|some", value: $0.value)) == nil, - let ivarName = $0.label - else { return nil } - var objName = Inspector.typeName(value: $0.value) - objName = objName[18...size - var offset = MemoryLayout.stride - envObjSize - let step = MemoryLayout.alignment - while offset + envObjSize > viewSize { - offset -= step - } - withUnsafeBytes(of: EnvObject.Forgery(object: nil)) { reference in - while offset >= 0 { - var copy = self - withUnsafeMutableBytes(of: ©) { bytes in - guard bytes[offset..) -> String { - let range = Range(uncheckedBounds: (lower: max(0, min(count, intRange.lowerBound)), - upper: min(count, max(0, intRange.upperBound)))) - let start = index(startIndex, offsetBy: range.lowerBound) - let end = index(start, offsetBy: range.upperBound - range.lowerBound) - return String(self[start ..< end]) - } -} diff --git a/Sources/ViewInspector/EnvironmentInjection.swift b/Sources/ViewInspector/EnvironmentInjection.swift new file mode 100644 index 00000000..4c6cb3d6 --- /dev/null +++ b/Sources/ViewInspector/EnvironmentInjection.swift @@ -0,0 +1,81 @@ +import SwiftUI + +// MARK: - EnvironmentObject injection + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Inspectable { + var missingEnvironmentObjects: [String] { + let prefix = "SwiftUI.EnvironmentObject<" + let mirror = Mirror(reflecting: self) + return mirror.children.compactMap { + let fullName = Inspector.typeName(value: $0.value, namespaced: true) + guard fullName.hasPrefix(prefix), + (try? Inspector.attribute(path: "_store|some", value: $0.value)) == nil, + let ivarName = $0.label + else { return nil } + var objName = Inspector.typeName(value: $0.value) + objName = objName[18...size + var offset = MemoryLayout.stride - envObjSize + let step = MemoryLayout.alignment + while offset + envObjSize > viewSize { + offset -= step + } + withUnsafeBytes(of: EnvObject.Forgery(object: nil)) { reference in + while offset >= 0 { + var copy = self + withUnsafeMutableBytes(of: ©) { bytes in + guard bytes[offset..) -> String { + let range = Range(uncheckedBounds: (lower: max(0, min(count, intRange.lowerBound)), + upper: min(count, max(0, intRange.upperBound)))) + let start = index(startIndex, offsetBy: range.lowerBound) + let end = index(start, offsetBy: range.upperBound - range.lowerBound) + return String(self[start ..< end]) + } +} diff --git a/Sources/ViewInspector/Inspector.swift b/Sources/ViewInspector/Inspector.swift index 13d0fc89..4da98e60 100644 --- a/Sources/ViewInspector/Inspector.swift +++ b/Sources/ViewInspector/Inspector.swift @@ -1,10 +1,10 @@ import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal struct Inspector { } +public struct Inspector { } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -extension Inspector { +internal extension Inspector { static func attribute(label: String, value: Any) throws -> Any { if label == "super", let superclass = Mirror(reflecting: value).superclassMirror { @@ -52,14 +52,25 @@ extension Inspector { prefixOnly: Bool = false) -> String { let typeName = namespaced ? String(reflecting: type) : String(describing: type) guard prefixOnly else { return typeName } - return typeName.components(separatedBy: "<").first! - } + let name = typeName.components(separatedBy: "<").first! + guard namespaced else { return name } + let string = NSMutableString(string: name) + let range = NSRange(location: 0, length: string.length) + namespaceSanitizeRegex.replaceMatches(in: string, options: [], range: range, withTemplate: "SwiftUI") + return String(string) + } + + private static var namespaceSanitizeRegex: NSRegularExpression = { + guard let regex = try? NSRegularExpression(pattern: "SwiftUI.\\(unknown context at .*\\)", options: []) + else { fatalError() } + return regex + }() } // MARK: - Attributes lookup @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -extension Inspector { +public extension Inspector { /** Use this function to lookup the struct content: @@ -67,8 +78,8 @@ extension Inspector { (lldb) po Inspector.print(view) as AnyObject ``` */ - public static func print(_ value: Any) -> String { - let tree = attributesTree(value: value, medium: .empty) + static func print(_ value: Any) -> String { + let tree = attributesTree(value: value, medium: .empty, visited: []) return typeName(value: value) + print(tree, level: 1) } @@ -96,22 +107,35 @@ extension Inspector { return needsNewLine ? "\n" : "" } - private static func attributesTree(value: Any, medium: Content.Medium) -> Any { + private static func attributesTree(value: Any, medium: Content.Medium, visited: [AnyObject]) -> Any { + var visited = visited + if type(of: value) is AnyClass { + let ref = value as AnyObject + guard !visited.contains(where: { $0 === ref }) + else { return " = { cyclic reference }" } + visited.append(ref) + } if let array = value as? [Any] { - return array.map { attributesTree(value: $0, medium: medium) } + return array.map { attributesTree(value: $0, medium: medium, visited: visited) } } let medium = (try? unwrap(content: Content(value, medium: medium)).medium) ?? medium - let mirror = Mirror(reflecting: value) + var mirror = Mirror(reflecting: value) + var children = Array(mirror.children) + while let superclass = mirror.superclassMirror { + mirror = superclass + children.append(contentsOf: superclass.children) + } var dict: [String: Any] = [:] - mirror.children.enumerated().forEach { child in + children.enumerated().forEach { child in let childName = child.element.label ?? "[\(child.offset)]" let childType = typeName(value: child.element.value) - dict[childName + ": " + childType] = attributesTree(value: child.element.value, medium: medium) + dict[childName + ": " + childType] = attributesTree( + value: child.element.value, medium: medium, visited: visited) } if let inspectable = value as? Inspectable, let content = try? inspectable.extractContent(environmentObjects: medium.environmentObjects) { let childType = typeName(value: content) - dict["body: " + childType] = attributesTree(value: content, medium: medium) + dict["body: " + childType] = attributesTree(value: content, medium: medium, visited: visited) } if dict.count == 0 { return " = " + String(describing: value) @@ -145,7 +169,7 @@ fileprivate extension Array { // MARK: - View Inspection @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -extension Inspector { +internal extension Inspector { static func viewsInContainer(view: Any, medium: Content.Medium) throws -> LazyGroup { let unwrappedContainer = try Inspector.unwrap(content: Content(view, medium: medium.resettingViewModifiers())) @@ -163,6 +187,7 @@ extension Inspector { return try unwrap(content: Content(view, medium: medium)) } + // swiftlint:disable cyclomatic_complexity static func unwrap(content: Content) throws -> Content { switch Inspector.typeName(value: content.view, prefixOnly: true) { case "Tree": @@ -177,6 +202,8 @@ extension Inspector { return try ViewType.ViewModifier.child(content) case "SubscriptionView": return try ViewType.SubscriptionView.child(content) + case "_UnaryViewAdaptor": + return try ViewType.UnaryViewAdaptor.child(content) case "_ConditionalContent": return try ViewType.ConditionalContent.child(content) case "EnvironmentReaderView": @@ -187,26 +214,30 @@ extension Inspector { return content } } + // swiftlint:enable cyclomatic_complexity static func guardType(value: Any, namespacedPrefixes: [String], inspectionCall: String) throws { - guard let firstPrefix = namespacedPrefixes.first - else { return } - let typeWithParams = typeName(type: type(of: value)) - let typePrefix = typeName(type: type(of: value), namespaced: true, prefixOnly: true) - if typePrefix == "SwiftUI.EnvironmentReaderView" { - if typeWithParams.contains("NavigationBarItemsKey") { - throw InspectionError.notSupported( - """ - Please insert '.navigationBarItems()' before \(inspectionCall) \ - for unwrapping the underlying view hierarchy. - """) - } else if typeWithParams.contains("_AnchorWritingModifier") { - throw InspectionError.notSupported( - "Unwrapping the view under popover is not supported on iOS 14.0 and 14.1") + + for prefix in namespacedPrefixes { + let withGenericParams = prefix.contains("<") + let typePrefix = typeName(type: type(of: value), namespaced: true, prefixOnly: !withGenericParams) + if typePrefix == "SwiftUI.EnvironmentReaderView" { + let typeWithParams = typeName(type: type(of: value)) + if typeWithParams.contains("NavigationBarItemsKey") { + throw InspectionError.notSupported( + """ + Please insert '.navigationBarItems()' before \(inspectionCall) \ + for unwrapping the underlying view hierarchy. + """) + } + } + if namespacedPrefixes.contains(typePrefix) { + return } } - guard namespacedPrefixes.contains(typePrefix) else { - throw InspectionError.typeMismatch(factual: typePrefix, expected: firstPrefix) + if let prefix = namespacedPrefixes.first { + let typePrefix = typeName(type: type(of: value), namespaced: true) + throw InspectionError.typeMismatch(factual: typePrefix, expected: prefix) } } } diff --git a/Sources/ViewInspector/Modifiers/AccessibilityModifiers.swift b/Sources/ViewInspector/Modifiers/AccessibilityModifiers.swift index ca41c993..e91752c3 100644 --- a/Sources/ViewInspector/Modifiers/AccessibilityModifiers.swift +++ b/Sources/ViewInspector/Modifiers/AccessibilityModifiers.swift @@ -6,101 +6,301 @@ import SwiftUI public extension InspectableView { func accessibilityLabel() throws -> InspectableView { - let text = try accessibilityElement( - "LabelKey", type: Text.self, call: "accessibilityLabel") + let text: Text + let call = "accessibilityLabel" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + text = try v3AccessibilityElement( + type: Text.self, call: call, { $0.accessibilityLabel("") }) + } else { + text = try v2AccessibilityElement("LabelKey", type: Text.self, call: call) + } let medium = content.medium.resettingViewModifiers() return try .init(try Inspector.unwrap(content: Content(text, medium: medium)), parent: self) } func accessibilityValue() throws -> InspectableView { - let text = try accessibilityElement( - "TypedValueKey", path: "value|some|description|some", - type: Text.self, call: "accessibilityValue") + let text: Text + let call = "accessibilityValue" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + text = try v3AccessibilityElement( + path: "some|description|some", type: Text.self, + call: call, { $0.accessibilityValue("") }) + } else { + text = try v2AccessibilityElement( + "TypedValueKey", path: "value|some|description|some", type: Text.self, call: call) + } let medium = content.medium.resettingViewModifiers() return try .init(try Inspector.unwrap(content: Content(text, medium: medium)), parent: self) } func accessibilityHint() throws -> InspectableView { - let text = try accessibilityElement( - "HintKey", type: Text.self, call: "accessibilityHint") + let text: Text + let call = "accessibilityHint" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + text = try v3AccessibilityElement( + type: Text.self, call: call, { $0.accessibilityHint("") }) + } else { + text = try v2AccessibilityElement("HintKey", type: Text.self, call: call) + } let medium = content.medium.resettingViewModifiers() return try .init(try Inspector.unwrap(content: Content(text, medium: medium)), parent: self) } func accessibilityHidden() throws -> Bool { - let visibility = try accessibilityElement( - "VisibilityKey", path: "value", type: (Any?).self, call: "accessibility(hidden:)") - switch visibility { - case let .some(value): - return String(describing: value) == "hidden" - case .none: - return false + let call = "accessibilityHidden" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + let value = try v3AccessibilityElement( + path: "value|rawValue", type: UInt32.self, + call: call, { $0.accessibilityHidden(true) }) + return value != 0 + } else { + let visibility = try v2AccessibilityElement( + "VisibilityKey", path: "value", type: (Any?).self, call: call) + switch visibility { + case let .some(value): + return String(describing: value) == "hidden" + case .none: + return false + } } } func accessibilityIdentifier() throws -> String { - return try accessibilityElement( - "IdentifierKey", type: String.self, call: "accessibility(identifier:)") + let call = "accessibilityIdentifier" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return try v3AccessibilityElement( + type: String.self, call: call, { $0.accessibilityIdentifier("") }) + } else { + return try v2AccessibilityElement("IdentifierKey", type: String.self, call: call) + } + } + + func accessibilitySortPriority() throws -> Double { + let call = "accessibilitySortPriority" + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return try v3AccessibilityElement( + type: Double.self, call: call, { $0.accessibilitySortPriority(0) }) + } else { + return try v2AccessibilityElement("SortPriorityKey", type: Double.self, call: call) + } } + @available(iOS, deprecated, introduced: 13.0) + @available(macOS, deprecated, introduced: 10.15) + @available(tvOS, deprecated, introduced: 13.0) + @available(watchOS, deprecated, introduced: 6) func accessibilitySelectionIdentifier() throws -> AnyHashable { - return try accessibilityElement( + return try v2AccessibilityElement( "SelectionIdentifierKey", type: AnyHashable.self, call: "accessibility(selectionIdentifier:)") } func accessibilityActivationPoint() throws -> UnitPoint { - return try accessibilityElement( + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return try v3AccessibilityElement( + path: "some|unitPoint", type: UnitPoint.self, + call: "accessibilityIdentifier", { $0.accessibilityActivationPoint(.center) }) + } else { + return try v2AccessibilityElement( "ActivationPointKey", path: "value|some|unitPoint", type: UnitPoint.self, call: "accessibility(activationPoint:)") + } + } + + func callAccessibilityAction(_ named: S) throws where S: StringProtocol { + try callAccessibilityAction(AccessibilityActionKind(named: Text(named))) } func callAccessibilityAction(_ kind: AccessibilityActionKind) throws { - let kindString = String(describing: kind) - let shortName = kindString - .components(separatedBy: CharacterSet(charactersIn: ".)")) - .filter { $0.count > 0 }.last! - let call = "accessibilityAction(.\(shortName))" + let shortName: String = { + if let name = try? kind.name().string() { + return "named: \"\(name)\"" + } + return "." + String(describing: kind) + .components(separatedBy: CharacterSet(charactersIn: ".)")) + .filter { $0.count > 0 }.last! + }() + let call = "accessibilityAction(\(shortName))" typealias Callback = (()) -> Void - let callback = try accessibilityAction(name: kindString, path: "box|action|kind", - type: Callback.self, call: call) + let callback: Callback + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + callback = try v3AccessibilityAction(kind: kind, type: Callback.self, call: call) + } else { + callback = try v2AccessibilityAction(kind: kind, type: Callback.self, call: call) + } callback(()) } func callAccessibilityAdjustableAction(_ direction: AccessibilityAdjustmentDirection = .increment) throws { typealias Callback = (AccessibilityAdjustmentDirection) -> Void - let callback = try accessibilityAction( - name: "AccessibilityAdjustableAction()", path: "box|action", - type: Callback.self, call: "accessibilityAdjustableAction") + let callback: Callback + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + callback = try v3AccessibilityAction( + name: "AccessibilityAdjustableAction", + type: Callback.self, + call: "accessibilityAdjustableAction") + } else { + callback = try v2AccessibilityAction( + name: "AccessibilityAdjustableAction()", + type: Callback.self, + call: "accessibilityAdjustableAction") + } callback(direction) } func callAccessibilityScrollAction(_ edge: Edge) throws { typealias Callback = (Edge) -> Void - let callback = try accessibilityAction( - name: "AccessibilityScrollAction()", path: "box|action", - type: Callback.self, call: "accessibilityScrollAction") + let callback: Callback + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + callback = try v3AccessibilityAction( + name: "AccessibilityScrollAction", + type: Callback.self, + call: "accessibilityScrollAction") + } else { + callback = try v2AccessibilityAction( + name: "AccessibilityScrollAction()", + type: Callback.self, + call: "accessibilityScrollAction") + } callback(edge) } +} + +// MARK: - Private + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension AccessibilityActionKind { + func name() throws -> InspectableView { + let view: Any = try { + if let view = try? Inspector.attribute(path: "kind|named", value: self) { + return view + } + return try Inspector.attribute(path: "kind|custom", value: self) + }() + return try .init(Content(view), parent: nil, index: nil) + } - func accessibilitySortPriority() throws -> Double { - return try accessibilityElement( - "SortPriorityKey", type: Double.self, call: "accessibility(sortPriority:)") + func isEqual(to other: AccessibilityActionKind) -> Bool { + if let lhsName = try? self.name().string(), + let rhsName = try? other.name().string() { + return lhsName == rhsName + } + return self == other } } -// MARK: - Private +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private struct AccessibilityProperty { + + let keyPointerValue: UInt64 + let value: Any + + init(property: Any) throws { + self.keyPointerValue = try Inspector.attribute( + path: "super|key|rawValue|pointerValue", value: property, type: UInt64.self) + self.value = try Inspector.attribute(path: "super|value", value: property) + } + + static var noisePointerValues: Set = { + let view1 = EmptyView().accessibility(label: Text("")) + let view2 = EmptyView().accessibility(hint: Text("")) + do { + let props1 = try view1.inspect() + .v3AccessibilityProperties(call: "") + .map { $0.keyPointerValue } + let props2 = try view2.inspect() + .v3AccessibilityProperties(call: "") + .map { $0.keyPointerValue } + return Set(props1).intersection(Set(props2)) + } catch { return .init() } + }() +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private extension InspectableView { + func v3AccessibilityElement( + path: String? = nil, type: T.Type, call: String, _ reference: (EmptyView) -> V + ) throws -> T where V: SwiftUI.View { + let noiseValues = AccessibilityProperty.noisePointerValues + guard let referenceValue = try reference(EmptyView()).inspect() + .v3AccessibilityProperties(call: call) + .map({ $0.keyPointerValue }) + .first(where: { !noiseValues.contains($0) }), + let property = try v3AccessibilityProperties(call: call) + .first(where: { $0.keyPointerValue == referenceValue }) + else { + throw InspectionError + .modifierNotFound(parent: Inspector.typeName(value: content.view), + modifier: call, index: 0) + } + if let path = path { + return try Inspector.attribute(path: path, value: property.value, type: T.self) + } + return try Inspector.cast(value: property.value, type: T.self) + } + + func v3AccessibilityAction(kind: AccessibilityActionKind, type: T.Type, call: String) throws -> T { + return try v3AccessibilityAction(trait: { action in + try Inspector.attribute( + path: "action|kind", value: action, type: AccessibilityActionKind.self) + .isEqual(to: kind) + }, type: type, call: call) + } + + func v3AccessibilityAction(name: String, type: T.Type, call: String) throws -> T { + return try v3AccessibilityAction(trait: { action in + Inspector.typeName(value: action).contains(name) + }, type: type, call: call) + } + + func v3AccessibilityAction(trait: (Any) throws -> Bool, type: T.Type, call: String) throws -> T { + let actions = try v3AccessibilityActions(call: call) + guard let action = actions.first(where: { (try? trait($0)) == true }) else { + throw InspectionError + .modifierNotFound(parent: Inspector.typeName(value: content.view), + modifier: call, index: 0) + } + return try Inspector.attribute(label: "handler", value: action, type: T.self) + } + + func v3AccessibilityActions(call: String) throws -> [Any] { + let noiseValues = AccessibilityProperty.noisePointerValues + guard let referenceValue = try EmptyView().accessibilityAction(.default, { }) + .inspect() + .v3AccessibilityProperties(call: call) + .map({ $0.keyPointerValue }) + .first(where: { !noiseValues.contains($0) }) + else { + throw InspectionError + .modifierNotFound(parent: Inspector.typeName(value: content.view), + modifier: call, index: 0) + } + return try v3AccessibilityProperties(call: call) + .filter({ $0.keyPointerValue == referenceValue }) + .compactMap { $0.value as? [Any] } + .flatMap { $0 } + .map { try Inspector.attribute(path: "base|base", value: $0) } + } + + func v3AccessibilityProperties(call: String) throws -> [AccessibilityProperty] { + return try modifierAttribute( + modifierName: "AccessibilityAttachmentModifier", + path: "modifier|storage|propertiesComponent", + type: [Any].self, call: call) + .map { try AccessibilityProperty(property: $0) } + } +} @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private extension InspectableView { - func accessibilityElement(_ name: String, path: String = "value|some", - type: T.Type, call: String) throws -> T { + func v2AccessibilityElement(_ name: String, path: String = "value|some", + type: T.Type, call: String) throws -> T { let item = try modifierAttribute( modifierName: "AccessibilityAttachmentModifier", path: "modifier|attachment|some|properties|plist|elements|some", type: Any.self, call: call) - guard let attribute = lookupAttributeWithName(name, item: item), + guard let attribute = v2LookupAttributeWithName(name, item: item), let value = try? Inspector.attribute(path: path, value: attribute) as? T else { throw InspectionError.modifierNotFound(parent: Inspector.typeName(value: content.view), modifier: call, index: 0) @@ -108,25 +308,35 @@ private extension InspectableView { return value } - func lookupAttributeWithName(_ name: String, item: Any) -> Any? { + func v2LookupAttributeWithName(_ name: String, item: Any) -> Any? { if Inspector.typeName(value: item).contains(name) { return item } if let nextItem = try? Inspector.attribute(path: "super|after|some", value: item) { - return lookupAttributeWithName(name, item: nextItem) + return v2LookupAttributeWithName(name, item: nextItem) } return nil } + + func v2AccessibilityAction(kind: AccessibilityActionKind, type: T.Type, call: String) throws -> T { + return try v2AccessibilityAction(type: type, call: call, trait: { handler in + try Inspector.attribute(path: "box|action|kind", value: handler, type: AccessibilityActionKind.self) + .isEqual(to: kind) + }) + } + func v2AccessibilityAction(name: String, type: T.Type, call: String) throws -> T { + return try v2AccessibilityAction(type: type, call: call, trait: { handler in + let actionName = try Inspector.attribute(path: "box|action", value: handler) + return name == String(describing: actionName) + }) + } - func accessibilityAction(name: String, path: String, type: T.Type, call: String) throws -> T { - let actionHandlers = try accessibilityElement( + func v2AccessibilityAction(type: T.Type, call: String, trait: (Any) throws -> Bool) throws -> T { + let actionHandlers = try v2AccessibilityElement( "ActionsKey", path: "value", type: [Any].self, call: call) - guard let handler = actionHandlers.first(where: { handler -> Bool in - guard let actionName = try? Inspector.attribute(path: path, value: handler) - else { return false } - return name == String(describing: actionName) - }), let callback = try? Inspector.attribute(path: "box|handler", value: handler) as? T + guard let handler = actionHandlers.first(where: { (try? trait($0)) == true }), + let callback = try? Inspector.attribute(path: "box|handler", value: handler) as? T else { throw InspectionError.modifierNotFound(parent: Inspector.typeName(value: content.view), modifier: call, index: 0) diff --git a/Sources/ViewInspector/Modifiers/EnvironmentModifiers.swift b/Sources/ViewInspector/Modifiers/EnvironmentModifiers.swift index b10b2796..1bbedfae 100644 --- a/Sources/ViewInspector/Modifiers/EnvironmentModifiers.swift +++ b/Sources/ViewInspector/Modifiers/EnvironmentModifiers.swift @@ -13,6 +13,11 @@ public extension InspectableView { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension InspectableView { func environment(_ reference: WritableKeyPath, call: String) throws -> T { + return try environment(reference, call: call, valueType: T.self) + } + + func environment(_ reference: WritableKeyPath, + call: String, valueType: V.Type) throws -> V { guard let modifier = content.medium.environmentModifiers.last(where: { modifier in guard let keyPath = try? modifier.keyPath() as? WritableKeyPath else { return false } @@ -21,7 +26,7 @@ internal extension InspectableView { throw InspectionError.modifierNotFound( parent: Inspector.typeName(value: content.view), modifier: call, index: 0) } - return try Inspector.cast(value: try modifier.value(), type: T.self) + return try Inspector.cast(value: try modifier.value(), type: V.self) } } @@ -33,8 +38,27 @@ internal extension Inspector { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal protocol EnvironmentModifier { + static func qualifiesAsEnvironmentModifier() -> Bool + func keyPath() throws -> Any + func value() throws -> Any +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension EnvironmentModifier { + func qualifiesAsEnvironmentModifier() -> Bool { + return Self.qualifiesAsEnvironmentModifier() + } +} + @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) extension ModifiedContent: EnvironmentModifier where Modifier: EnvironmentModifier { + + static func qualifiesAsEnvironmentModifier() -> Bool { + return Modifier.qualifiesAsEnvironmentModifier() + } + func keyPath() throws -> Any { return try Inspector.attribute(label: "modifier", value: self, type: Modifier.self).keyPath() @@ -47,19 +71,39 @@ extension ModifiedContent: EnvironmentModifier where Modifier: EnvironmentModifi } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal protocol EnvironmentModifier { - func keyPath() throws -> Any - func value() throws -> Any +extension _EnvironmentKeyWritingModifier: EnvironmentModifier { + + static func qualifiesAsEnvironmentModifier() -> Bool { + return true + } + + func keyPath() throws -> Any { + return try Inspector.attribute(label: "keyPath", value: self) + } + + func value() throws -> Any { + return try Inspector.attribute(label: "value", value: self) + } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -extension _EnvironmentKeyWritingModifier: EnvironmentModifier { +extension _EnvironmentKeyTransformModifier: EnvironmentModifier { + + static func qualifiesAsEnvironmentModifier() -> Bool { + #if !os(macOS) && !targetEnvironment(macCatalyst) + if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, *), + Value.self == TextInputAutocapitalization.self { + return true + } + #endif + return false + } func keyPath() throws -> Any { return try Inspector.attribute(label: "keyPath", value: self) } func value() throws -> Any { - return try Inspector.attribute(label: "value", value: self) + return try Inspector.attribute(label: "transform", value: self) } } diff --git a/Sources/ViewInspector/Modifiers/SizingModifiers.swift b/Sources/ViewInspector/Modifiers/SizingModifiers.swift index 2206546a..3039d1cd 100644 --- a/Sources/ViewInspector/Modifiers/SizingModifiers.swift +++ b/Sources/ViewInspector/Modifiers/SizingModifiers.swift @@ -75,78 +75,60 @@ public extension InspectableView { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public extension InspectableView { - private struct PaddingAttributes { - let edgeInsets: EdgeInsets? - let edges: Edge.Set - } - func padding() throws -> EdgeInsets { - return try modifierAttribute( - modifierName: "_PaddingLayout", path: "modifier|insets", - type: EdgeInsets.self, call: "padding") - } - - func padding(_ edge: Edge.Set) throws -> CGFloat { - let attributes = try self.paddingAttributes() - for attribute in attributes { - if attribute.edges.contains(edge) { - if let value = edgeValue(attribute: attribute, edge: edge) { - return value - } - } + let attr = paddingAttributes() + guard attr.count > 0 else { + throw noPaddingModifierError() } - throw InspectionError.modifierNotFound( - parent: Inspector.typeName(value: self), modifier: "padding", index: 0) - } - - func hasPadding(_ edge: Edge.Set = .all) throws -> Bool { - let attributes = try self.paddingAttributes() - for attribute in attributes { - if attribute.edges.contains(edge) { - return true + do { + return .init(top: try attr.cumulativeValue(edge: .top) ?? 0, + leading: try attr.cumulativeValue(edge: .leading) ?? 0, + bottom: try attr.cumulativeValue(edge: .bottom) ?? 0, + trailing: try attr.cumulativeValue(edge: .trailing) ?? 0) + } catch let error { + if attr.allSatisfy({ $0.edges == .all }) { + throw InspectionError.notSupported( + "Please use `hasPadding(_:)` for inspecting padding without explicit value.") } + throw error } - return false } - private func edgeValue(attribute: PaddingAttributes, edge: Edge.Set) -> CGFloat? { - guard let edgeInsets = attribute.edgeInsets else { - return nil - } - var result = [CGFloat]() - if edge.contains(.top) { - result.append(edgeInsets.top) - } - if edge.contains(.bottom) { - result.append(edgeInsets.bottom) - } - if edge.contains(.trailing) { - result.append(edgeInsets.trailing) + func padding(_ edge: Edge.Set) throws -> CGFloat { + let attr = paddingAttributes() + let edges = edge.individualEdges + guard edges.count > 0 else { + throw InspectionError.notSupported("No edge is specified") } - if edge.contains(.leading) { - result.append(edgeInsets.leading) + let values = try edges.map { singleEdge -> CGFloat in + guard let value = try attr.cumulativeValue(edge: singleEdge.edgeSet) else { + throw noPaddingModifierError() + } + return value } - if hasSingleValue(result) { - return result[0] + guard values.areAllEqual() else { + throw InspectionError.notSupported( + """ + Insets for edges '\(edges)' have different values, \ + consider calling `padding` individually per edge. + """ + ) } - return nil + return values[0] } - private func hasSingleValue(_ array: [CGFloat]) -> Bool { - if array.count == 0 { - return false - } - return array.dropLast().allSatisfy { $0 == array.last } + func hasPadding(_ edge: Edge.Set = .all) -> Bool { + return paddingAttributes().contains(where: { $0.edges.contains(edge) }) } - private func paddingAttributes() throws -> [PaddingAttributes] { - - return try modifiersMatching({ $0.modifierType.contains("_PaddingLayout") }) + private func paddingAttributes() -> [Inspector.PaddingAttributes] { + return modifiersMatching({ $0.modifierType.contains("_PaddingLayout") }) .enumerated() - .map { index, modifier -> PaddingAttributes in - let edges = try modifierAttribute( + .compactMap { index, modifier -> Inspector.PaddingAttributes? in + guard let edges = try? modifierAttribute( modifierName: "_PaddingLayout", path: "modifier|edges", type: SwiftUI.Edge.Set.self, call: "padding", index: index) + else { return nil } let insets: EdgeInsets? do { insets = try modifierAttribute( @@ -158,4 +140,82 @@ public extension InspectableView { return .init(edgeInsets: insets, edges: edges) } } + + private func noPaddingModifierError() -> Error { + return InspectionError.modifierNotFound( + parent: Inspector.typeName(value: content.view), + modifier: "padding", index: 0) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension Inspector { + struct PaddingAttributes { + let edgeInsets: EdgeInsets? + let edges: Edge.Set + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension Edge.Set { + var individualEdges: [Edge] { + return [Edge.top, .bottom, .leading, .trailing] + .filter { contains($0.edgeSet) } + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension Edge { + var edgeSet: Edge.Set { + switch self { + case .top: return .top + case .bottom: return .bottom + case .leading: return .leading + case .trailing: return .trailing + } + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension RandomAccessCollection where Element == Inspector.PaddingAttributes { + func cumulativeValue(edge: Edge.Set) throws -> CGFloat? { + let undefinedInsetError: (Edge) -> Error = { edge in + return InspectionError.notSupported( + """ + Undefined inset for '\(edge)' edge. Consider calling `hasPadding(_:)` \ + instead to assure a default padding is applied. + """) + } + let insets = try compactMap { attr -> CGFloat? in + if edge == .top, attr.edges.contains(.top) { + guard let insets = attr.edgeInsets + else { throw undefinedInsetError(.top) } + return insets.top + } + if edge == .bottom, attr.edges.contains(.bottom) { + guard let insets = attr.edgeInsets + else { throw undefinedInsetError(.bottom) } + return insets.bottom + } + if edge == .trailing, attr.edges.contains(.trailing) { + guard let insets = attr.edgeInsets + else { throw undefinedInsetError(.trailing) } + return insets.trailing + } + if edge == .leading, attr.edges.contains(.leading) { + guard let insets = attr.edgeInsets + else { throw undefinedInsetError(.leading) } + return insets.leading + } + return nil + } + return insets.count == 0 ? nil : insets.reduce(0, +) + } +} + +private extension RandomAccessCollection where Element: Equatable { + func areAllEqual() -> Bool { + guard let first = self.first else { return true } + return !contains(where: { $0 != first }) + } } diff --git a/Sources/ViewInspector/Modifiers/TextInputModifiers.swift b/Sources/ViewInspector/Modifiers/TextInputModifiers.swift index 2c4b6188..504afd67 100644 --- a/Sources/ViewInspector/Modifiers/TextInputModifiers.swift +++ b/Sources/ViewInspector/Modifiers/TextInputModifiers.swift @@ -2,19 +2,17 @@ import SwiftUI // MARK: - Adjusting Text in a View -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension InspectableView { - #if !os(macOS) + #if (os(iOS) || os(tvOS)) && !targetEnvironment(macCatalyst) func textContentType() throws -> UITextContentType? { let reference = EmptyView().textContentType(.emailAddress) let keyPath = try Inspector.environmentKeyPath(Optional.self, reference) let value = try environment(keyPath, call: "textContentType") return value.flatMap { UITextContentType(rawValue: $0) } } - #endif - - #if os(iOS) || os(tvOS) + func keyboardType() throws -> UIKeyboardType { let reference = EmptyView().keyboardType(.default) let keyPath = try Inspector.environmentKeyPath(Int.self, reference) @@ -24,6 +22,21 @@ public extension InspectableView { func autocapitalization() throws -> UITextAutocapitalizationType { let reference = EmptyView().autocapitalization(.none) + if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) { + typealias Closure = (inout TextInputAutocapitalization) -> Void + if let keyPath = try? Inspector + .environmentKeyPath(TextInputAutocapitalization.self, reference) { + let closure = try environment(keyPath, call: "autocapitalization", valueType: Closure.self) + var value = TextInputAutocapitalization.never + closure(&value) + let behavior = try Inspector.attribute(label: "behavior", value: value) + let stringValue = String(describing: behavior) + guard let style = TextInputAutocapitalization.Behavior(rawValue: stringValue) else { + throw InspectionError.notSupported("Unknown TextInputAutocapitalization.Behavior: \(stringValue)") + } + return style.autocapitalizationType + } + } let keyPath = try Inspector.environmentKeyPath(Int.self, reference) let value = try environment(keyPath, call: "autocapitalization") return UITextAutocapitalizationType(rawValue: value)! @@ -91,6 +104,7 @@ public extension InspectableView { return false } + #if !os(watchOS) func disableAutocorrection() -> Bool { let reference = EmptyView().disableAutocorrection(false) if let keyPath = try? Inspector.environmentKeyPath(Optional.self, reference), @@ -99,6 +113,7 @@ public extension InspectableView { } return false } + #endif func flipsForRightToLeftLayoutDirection() -> Bool { return modifiersMatching({ $0.modifierType == "_FlipForRTLEffect" }, @@ -109,3 +124,21 @@ public extension InspectableView { .contains(true) } } + +#if (os(iOS) || os(tvOS)) && !targetEnvironment(macCatalyst) +@available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) +extension TextInputAutocapitalization { + enum Behavior: String { + case never, words, sentences, characters + + var autocapitalizationType: UITextAutocapitalizationType { + switch self { + case .never: return .none + case .words: return .words + case .sentences: return .sentences + case .characters: return .allCharacters + } + } + } +} +#endif diff --git a/Sources/ViewInspector/Modifiers/VisualEffectModifiers.swift b/Sources/ViewInspector/Modifiers/VisualEffectModifiers.swift index 7a9113f2..46c76d0a 100644 --- a/Sources/ViewInspector/Modifiers/VisualEffectModifiers.swift +++ b/Sources/ViewInspector/Modifiers/VisualEffectModifiers.swift @@ -127,20 +127,22 @@ public extension InspectableView { return shape.cornerSize.width } - func mask() throws -> InspectableView { - return try contentForModifierLookup.mask(parent: self) + func mask(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.mask(parent: self, index: index) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func mask(parent: UnwrappedView) throws -> InspectableView { + func mask(parent: UnwrappedView, index: Int?) throws -> InspectableView { let rootView = try modifierAttribute( modifierName: "_MaskEffect", path: "modifier|mask", - type: Any.self, call: "mask") + type: Any.self, call: "mask", index: index ?? 0) let medium = self.medium.resettingViewModifiers() + let call = ViewType.inspectionCall( + base: "mask(\(ViewType.indexPlaceholder))", index: index) return try .init(try Inspector.unwrap(content: Content(rootView, medium: medium)), - parent: parent, call: "mask()") + parent: parent, call: call, index: index) } } diff --git a/Sources/ViewInspector/PopupPresenter.swift b/Sources/ViewInspector/PopupPresenter.swift new file mode 100644 index 00000000..c702457e --- /dev/null +++ b/Sources/ViewInspector/PopupPresenter.swift @@ -0,0 +1,192 @@ +import SwiftUI + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public protocol BasePopupPresenter { + func buildPopup() throws -> Any + func dismissPopup() + func content() throws -> ViewInspector.Content + var isAlertPresenter: Bool { get } + var isActionSheetPresenter: Bool { get } + var isPopoverPresenter: Bool { get } + var isSheetPresenter: Bool { get } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public protocol PopupPresenter: BasePopupPresenter { + associatedtype Popup + var isPresented: Binding { get } + var popupBuilder: () -> Popup { get } + var onDismiss: (() -> Void)? { get } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public protocol ItemPopupPresenter: BasePopupPresenter { + associatedtype Popup + associatedtype Item: Identifiable + var item: Binding { get } + var popupBuilder: (Item) -> Popup { get } + var onDismiss: (() -> Void)? { get } +} + +// MARK: - Extensions + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension BasePopupPresenter { + func subject(_ type: T.Type) -> String { + if isPopoverPresenter { return "Popover" } + if isSheetPresenter { return "Sheet" } + return Inspector.typeName(type: T.self) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension PopupPresenter { + func buildPopup() throws -> Any { + guard isPresented.wrappedValue else { + throw InspectionError.viewNotFound(parent: subject(Popup.self)) + } + return popupBuilder() + } + + func dismissPopup() { + isPresented.wrappedValue = false + onDismiss?() + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ItemPopupPresenter { + func buildPopup() throws -> Any { + guard let value = item.wrappedValue else { + throw InspectionError.viewNotFound(parent: subject(Popup.self)) + } + return popupBuilder(value) + } + + func dismissPopup() { + item.wrappedValue = nil + onDismiss?() + } +} + +// MARK: - Alert + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension PopupPresenter where Popup == Alert { + var isAlertPresenter: Bool { true } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ItemPopupPresenter where Popup == Alert { + var isAlertPresenter: Bool { true } +} + +// MARK: - ActionSheet + +@available(iOS 13.0, tvOS 13.0, *) +@available(macOS, unavailable) +public extension PopupPresenter where Popup == ActionSheet { + var isActionSheetPresenter: Bool { true } +} + +@available(iOS 13.0, tvOS 13.0, *) +@available(macOS, unavailable) +public extension ItemPopupPresenter where Popup == ActionSheet { + var isActionSheetPresenter: Bool { true } +} + +// MARK: - Popover, Sheet & FullScreenCover + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewModifier where Self: BasePopupPresenter { + func content() throws -> ViewInspector.Content { + let view = body(content: _ViewModifier_Content()) + return try view.inspect().viewModifierContent().content + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewModifier where Self: PopupPresenter { + var isPopoverPresenter: Bool { + return (try? content().standardPopoverModifier()) != nil + } + var isSheetPresenter: Bool { + return (try? content().standardSheetModifier()) != nil + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewModifier where Self: ItemPopupPresenter { + var isPopoverPresenter: Bool { + return (try? content().standardPopoverModifier()) != nil + } + var isSheetPresenter: Bool { + return (try? content().standardSheetModifier()) != nil + } +} + +// MARK: - Default + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension BasePopupPresenter { + var isAlertPresenter: Bool { false } + var isActionSheetPresenter: Bool { false } + var isPopoverPresenter: Bool { false } + var isSheetPresenter: Bool { false } +} + +// MARK: - PopupContainer + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension ViewType { + struct PopupContainer: CustomViewIdentityMapping { + let popup: Any + let presenter: BasePopupPresenter + var viewTypeForSearch: KnownViewType.Type { Popup.self } + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension ViewType.PopupContainer { + static var typePrefix: String { + "ViewInspector.ViewType.PopupContainer" + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Content { + func popup( + parent: UnwrappedView, index: Int?, + name: String = Inspector.typeName(type: Popup.self), + modifierPredicate: ModifierLookupClosure, + standardPredicate: (String) throws -> Any + ) throws -> InspectableView { + guard let popupPresenter = try? self.modifierAttribute( + modifierLookup: modifierPredicate, path: "modifier", + type: BasePopupPresenter.self, call: "", index: index ?? 0) + else { + _ = try standardPredicate(name) + throw InspectionError.notSupported( + """ + Please refer to the Guide for inspecting the \(name): \ + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#\(name.lowercased()) + """) + } + let popup: Any = try { + do { + return try popupPresenter.buildPopup() + } catch { + if case InspectionError.viewNotFound = error { + throw InspectionError.viewNotFound(parent: name) + } + throw error + } + }() + let container = ViewType.PopupContainer(popup: popup, presenter: popupPresenter) + let medium = self.medium.resettingViewModifiers() + let content = Content(container, medium: medium) + let call = ViewType.inspectionCall( + base: Popup.inspectionCall(typeName: name), index: index) + return try .init(content, parent: parent, call: call, index: index) + } +} diff --git a/Sources/ViewInspector/SwiftUI/ActionSheet.swift b/Sources/ViewInspector/SwiftUI/ActionSheet.swift index b6b4bfe3..9ff0bafc 100644 --- a/Sources/ViewInspector/SwiftUI/ActionSheet.swift +++ b/Sources/ViewInspector/SwiftUI/ActionSheet.swift @@ -6,10 +6,8 @@ import SwiftUI public extension ViewType { struct ActionSheet: KnownViewType { - public static var typePrefix: String = "ViewType.ActionSheet.Container" - public static var namespacedPrefixes: [String] { - return ["ViewInspector." + typePrefix] - } + public static var typePrefix: String = ViewType.PopupContainer.typePrefix + public static var namespacedPrefixes: [String] { [typePrefix] } public static func inspectionCall(typeName: String) -> String { return "actionSheet(\(ViewType.indexPlaceholder))" } @@ -32,32 +30,21 @@ public extension InspectableView { internal extension Content { func actionSheet(parent: UnwrappedView, index: Int?) throws -> InspectableView { - guard let sheetBuilder = try? self.modifierAttribute( - modifierLookup: { isActionSheetBuilder(modifier: $0) }, path: "modifier", - type: ActionSheetBuilder.self, call: "", index: index ?? 0) - else { - _ = try self.modifier({ - $0.modifierType == "IdentifiedPreferenceTransformModifier" - || $0.modifierType.contains("AlertTransformModifier") - }, call: "actionSheet") - throw InspectionError.notSupported( - """ - Please refer to the Guide for inspecting the ActionSheet: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#actionsheet - """) - } - let sheet = try sheetBuilder.buildSheet() - let container = ViewType.ActionSheet.Container(sheet: sheet, builder: sheetBuilder) - let medium = self.medium.resettingViewModifiers() - let content = Content(container, medium: medium) - let call = ViewType.inspectionCall( - base: ViewType.ActionSheet.inspectionCall(typeName: ""), index: index) - return try .init(content, parent: parent, call: call, index: index) + return try popup(parent: parent, index: index, + modifierPredicate: isActionSheetBuilder(modifier:), + standardPredicate: standardActionSheetModifier) + } + + func standardActionSheetModifier(_ name: String = "ActionSheet") throws -> Any { + return try self.modifier({ + $0.modifierType == "IdentifiedPreferenceTransformModifier" + || $0.modifierType.contains("AlertTransformModifier") + }, call: name.firstLetterLowercased) } func actionSheetsForSearch() -> [ViewSearch.ModifierIdentity] { let count = medium.viewModifiers - .compactMap { isActionSheetBuilder(modifier: $0) } + .filter(isActionSheetBuilder(modifier:)) .count return Array(0.. Bool { - return (try? Inspector.attribute( - label: "modifier", value: modifier, type: ActionSheetBuilder.self)) != nil - } -} - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -internal extension ViewType.ActionSheet { - struct Container: CustomViewIdentityMapping { - let sheet: SwiftUI.ActionSheet - let builder: ActionSheetBuilder - - var viewTypeForSearch: KnownViewType.Type { ViewType.ActionSheet.self } + let modifier = try? Inspector.attribute( + label: "modifier", value: modifier, type: BasePopupPresenter.self) + return modifier?.isActionSheetPresenter == true } } @@ -106,6 +83,12 @@ public extension InspectableView where View == ViewType.ActionSheet { return try allViews.element(at: index + 2) .asInspectableView(ofType: ViewType.AlertButton.self) } + + func dismiss() throws { + let container = try Inspector.cast( + value: content.view, type: ViewType.PopupContainer.self) + container.presenter.dismissPopup() + } } // MARK: - Non Standard Children @@ -114,18 +97,18 @@ public extension InspectableView where View == ViewType.ActionSheet { extension ViewType.ActionSheet: SupplementaryChildren { static func supplementaryChildren(_ parent: UnwrappedView) throws -> LazyGroup { let buttons = try Inspector.attribute( - path: "sheet|buttons", value: parent.content.view, type: [Any].self) + path: "popup|buttons", value: parent.content.view, type: [Any].self) return .init(count: 2 + buttons.count) { index in let medium = parent.content.medium.resettingViewModifiers() switch index { case 0: - let view = try Inspector.attribute(path: "sheet|title", value: parent.content.view) + let view = try Inspector.attribute(path: "popup|title", value: parent.content.view) let content = try Inspector.unwrap(content: Content(view, medium: medium)) return try InspectableView( content, parent: parent, call: "title()") case 1: let maybeView = try Inspector.attribute( - path: "sheet|message", value: parent.content.view, type: Text?.self) + path: "popup|message", value: parent.content.view, type: Text?.self) guard let view = maybeView else { throw InspectionError.viewNotFound(parent: "message") } @@ -145,56 +128,3 @@ extension ViewType.ActionSheet: SupplementaryChildren { } } } - -// MARK: - ActionSheet inspection protocols - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -public protocol ActionSheetBuilder: SystemPopupPresenter { - func buildSheet() throws -> ActionSheet -} - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -public protocol ActionSheetProvider: ActionSheetBuilder { - var isPresented: Binding { get } - var sheetBuilder: () -> ActionSheet { get } -} - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -public protocol ActionSheetItemProvider: ActionSheetBuilder { - associatedtype Item: Identifiable - var item: Binding { get } - var sheetBuilder: (Item) -> ActionSheet { get } -} - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -public extension ActionSheetProvider { - func buildSheet() throws -> ActionSheet { - guard isPresented.wrappedValue else { - throw InspectionError.viewNotFound(parent: "ActionSheet") - } - return sheetBuilder() - } - - func dismissPopup() { - isPresented.wrappedValue = false - } -} - -@available(iOS 13.0, tvOS 13.0, *) -@available(macOS, unavailable) -public extension ActionSheetItemProvider { - func buildSheet() throws -> ActionSheet { - guard let value = item.wrappedValue else { - throw InspectionError.viewNotFound(parent: "ActionSheet") - } - return sheetBuilder(value) - } - - func dismissPopup() { - item.wrappedValue = nil - } -} diff --git a/Sources/ViewInspector/SwiftUI/Alert.swift b/Sources/ViewInspector/SwiftUI/Alert.swift index d7822222..61141225 100644 --- a/Sources/ViewInspector/SwiftUI/Alert.swift +++ b/Sources/ViewInspector/SwiftUI/Alert.swift @@ -6,9 +6,10 @@ import SwiftUI public extension ViewType { struct Alert: KnownViewType { - public static var typePrefix: String = "ViewType.Alert.Container" + public static var typePrefix: String = ViewType.PopupContainer.typePrefix + static var typePrefixIOS15: String = "AlertModifier" public static var namespacedPrefixes: [String] { - return ["ViewInspector." + typePrefix] + [typePrefix, "SwiftUI." + typePrefixIOS15] } public static func inspectionCall(typeName: String) -> String { return "alert(\(ViewType.indexPlaceholder))" @@ -30,32 +31,33 @@ public extension InspectableView { internal extension Content { func alert(parent: UnwrappedView, index: Int?) throws -> InspectableView { - guard let alertBuilder = try? self.modifierAttribute( - modifierLookup: { isAlertBuilder(modifier: $0) }, path: "modifier", - type: AlertBuilder.self, call: "", index: index ?? 0) - else { - _ = try self.modifier({ - $0.modifierType == "IdentifiedPreferenceTransformModifier" - || $0.modifierType.contains("AlertTransformModifier") - }, call: "alert") - throw InspectionError.notSupported( - """ - Please refer to the Guide for inspecting the Alert: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#alert-sheet-and-actionsheet - """) + do { + return try popup(parent: parent, index: index, + modifierPredicate: isDeprecatedAlertPresenter(modifier:), + standardPredicate: deprecatedStandardAlertModifier) + } catch { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), + let alert = try? alertIOS15(parent: parent, index: index) { + return alert + } else { + throw error + } } - let alert = try alertBuilder.buildAlert() - let container = ViewType.Alert.Container(alert: alert, builder: alertBuilder) - let medium = self.medium.resettingViewModifiers() - let content = Content(container, medium: medium) - let call = ViewType.inspectionCall( - base: ViewType.Alert.inspectionCall(typeName: ""), index: index) - return try .init(content, parent: parent, call: call, index: index) + } + + private func deprecatedStandardAlertModifier(_ name: String = "Alert") throws -> Any { + return try self.modifier({ + $0.modifierType == "IdentifiedPreferenceTransformModifier" + || $0.modifierType.contains("AlertTransformModifier") + }, call: name.firstLetterLowercased) } func alertsForSearch() -> [ViewSearch.ModifierIdentity] { let count = medium.viewModifiers - .compactMap { isAlertBuilder(modifier: $0) } + .filter { modifier in + isDeprecatedAlertPresenter(modifier: modifier) + || isAlertIOS15(modifier: modifier) + } .count return Array(0.. Bool { - return (try? Inspector.attribute( - label: "modifier", value: modifier, type: AlertBuilder.self)) != nil + private func isDeprecatedAlertPresenter(modifier: Any) -> Bool { + let modifier = try? Inspector.attribute( + label: "modifier", value: modifier, type: BasePopupPresenter.self) + return modifier?.isAlertPresenter == true } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal extension ViewType.Alert { - struct Container: CustomViewIdentityMapping { - let alert: SwiftUI.Alert - let builder: AlertBuilder - - var viewTypeForSearch: KnownViewType.Type { ViewType.Alert.self } + + // MARK: - iOS 15 + + var isIOS15Modifier: Bool { + let type = ViewType.PopupContainer.self + return (try? Inspector.cast(value: view, type: type)) == nil + } + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func alertIOS15(parent: UnwrappedView, index: Int?) throws -> InspectableView { + let modifier = try self.modifierAttribute( + modifierLookup: isAlertIOS15(modifier:), path: "modifier", + type: Any.self, call: "alert", index: index ?? 0) + let medium = self.medium.resettingViewModifiers() + let content = Content(modifier, medium: medium) + let call = ViewType.inspectionCall( + base: ViewType.Alert.inspectionCall(typeName: ""), index: index) + let view = try InspectableView( + content, parent: parent, call: call, index: index) + guard try view.isPresentedBinding().wrappedValue else { + throw InspectionError.viewNotFound(parent: "Alert") + } + return view + } + + private func isAlertIOS15(modifier: Any) -> Bool { + guard let modifier = modifier as? ModifierNameProvider + else { return false } + return modifier.modifierType.contains(ViewType.Alert.typePrefixIOS15) } } @@ -90,9 +113,9 @@ public extension InspectableView where View == ViewType.Alert { .asInspectableView(ofType: ViewType.Text.self) } - func message() throws -> InspectableView { + func message() throws -> InspectableView { return try View.supplementaryChildren(self).element(at: 1) - .asInspectableView(ofType: ViewType.Text.self) + .asInspectableView(ofType: ViewType.ClassifiedView.self) } func primaryButton() throws -> InspectableView { @@ -104,6 +127,30 @@ public extension InspectableView where View == ViewType.Alert { return try View.supplementaryChildren(self).element(at: 3) .asInspectableView(ofType: ViewType.AlertButton.self) } + + func dismiss() throws { + do { + let container = try Inspector.cast( + value: content.view, type: ViewType.PopupContainer.self) + container.presenter.dismissPopup() + } catch { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), + let binding = try? isPresentedBinding() { + binding.wrappedValue = false + } else { + throw error + } + } + } +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +public extension InspectableView where View == ViewType.Alert { + + func actions() throws -> InspectableView { + return try View.supplementaryChildren(self).element(at: 2) + .asInspectableView(ofType: ViewType.ClassifiedView.self) + } } // MARK: - Non Standard Children @@ -111,31 +158,43 @@ public extension InspectableView where View == ViewType.Alert { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) extension ViewType.Alert: SupplementaryChildren { static func supplementaryChildren(_ parent: UnwrappedView) throws -> LazyGroup { - return .init(count: 4) { index in + let iOS15Modifier = parent.content.isIOS15Modifier + return .init(count: iOS15Modifier ? 3 : 4) { index in let medium = parent.content.medium.resettingViewModifiers() switch index { case 0: - let view = try Inspector.attribute(path: "alert|title", value: parent.content.view) + let path = iOS15Modifier ? "title" : "popup|title" + let view = try Inspector.attribute(path: path, value: parent.content.view) let content = try Inspector.unwrap(content: Content(view, medium: medium)) - return try InspectableView( - content, parent: parent, call: "title()") + return try InspectableView(content, parent: parent, call: "title()") case 1: - let maybeView = try Inspector.attribute( - path: "alert|message", value: parent.content.view, type: Text?.self) - guard let view = maybeView else { - throw InspectionError.viewNotFound(parent: "message") + let path = iOS15Modifier ? "message" : "popup|message" + do { + let view = try Inspector.attribute(path: path, value: parent.content.view) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + return try InspectableView( + content, parent: parent, call: "message()") + } catch { + if let inspError = error as? InspectionError, + case .viewNotFound = inspError { + throw InspectionError.viewNotFound(parent: "message") + } + throw error } - let content = try Inspector.unwrap(content: Content(view, medium: medium)) - return try InspectableView( - content, parent: parent, call: "message()") case 2: - let view = try Inspector.attribute(path: "alert|primaryButton", value: parent.content.view) + if iOS15Modifier { + let view = try Inspector.attribute(path: "actions", value: parent.content.view) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + return try InspectableView( + content, parent: parent, call: "actions()") + } + let view = try Inspector.attribute(path: "popup|primaryButton", value: parent.content.view) let content = try Inspector.unwrap(content: Content(view, medium: medium)) return try InspectableView( content, parent: parent, call: "primaryButton()") default: let maybeView = try Inspector.attribute( - path: "alert|secondaryButton", value: parent.content.view, type: Alert.Button?.self) + path: "popup|secondaryButton", value: parent.content.view, type: Alert.Button?.self) guard let view = maybeView else { throw InspectionError.viewNotFound(parent: "secondaryButton") } @@ -205,8 +264,8 @@ public extension InspectableView where View == ViewType.AlertButton { func tap() throws { guard let container = self.parentView?.content.view, let presenter = try? Inspector.attribute( - label: "builder", value: container, - type: SystemPopupPresenter.self) + label: "presenter", value: container, + type: BasePopupPresenter.self) else { throw InspectionError.parentViewNotFound(view: "Alert.Button") } presenter.dismissPopup() typealias Callback = () -> Void @@ -216,55 +275,10 @@ public extension InspectableView where View == ViewType.AlertButton { } } -// MARK: - Alert inspection protocols - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol SystemPopupPresenter { - func dismissPopup() -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol AlertBuilder: SystemPopupPresenter { - func buildAlert() throws -> Alert -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol AlertProvider: AlertBuilder { - var isPresented: Binding { get } - var alertBuilder: () -> Alert { get } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol AlertItemProvider: AlertBuilder { - associatedtype Item: Identifiable - var item: Binding { get } - var alertBuilder: (Item) -> Alert { get } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public extension AlertProvider { - func buildAlert() throws -> Alert { - guard isPresented.wrappedValue else { - throw InspectionError.viewNotFound(parent: "Alert") - } - return alertBuilder() - } - - func dismissPopup() { - isPresented.wrappedValue = false - } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public extension AlertItemProvider { - func buildAlert() throws -> Alert { - guard let value = item.wrappedValue else { - throw InspectionError.viewNotFound(parent: "Alert") - } - return alertBuilder(value) - } - - func dismissPopup() { - item.wrappedValue = nil +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private extension InspectableView where View == ViewType.Alert { + func isPresentedBinding() throws -> Binding { + return try Inspector.attribute( + label: "isPresented", value: content.view, type: Binding.self) } } diff --git a/Sources/ViewInspector/SwiftUI/Button.swift b/Sources/ViewInspector/SwiftUI/Button.swift index 8be7c1b5..f1e0714f 100644 --- a/Sources/ViewInspector/SwiftUI/Button.swift +++ b/Sources/ViewInspector/SwiftUI/Button.swift @@ -32,7 +32,13 @@ public extension InspectableView where View: MultipleViewContent { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) extension ViewType.Button: SupplementaryChildrenLabelView { - static var labelViewPath: String { "_label" } + static var labelViewPath: String { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return "label" + } else { + return "_label" + } + } } // MARK: - Custom Attributes @@ -52,6 +58,14 @@ public extension InspectableView where View == ViewType.Button { .attribute(label: "action", value: content.view, type: Callback.self) callback() } + + #if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func role() throws -> ButtonRole? { + return try Inspector.attribute( + label: "role", value: content.view, type: ButtonRole?.self) + } + #endif } // MARK: - Global View Modifiers @@ -93,23 +107,53 @@ public extension PrimitiveButtonStyle { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension ButtonStyleConfiguration { - private struct Allocator { + private struct Allocator3 { + let data: (Bool, Bool, Bool) + init(flag: Bool) { + data = (false, false, flag) + } + } + private struct Allocator24 { let data: (Int64, Int64, Int64) init(flag: Bool) { data = (flag ? -1 : 0, 0, 0) } } init(isPressed: Bool) { - self = unsafeBitCast(Allocator(flag: isPressed), to: Self.self) + switch MemoryLayout.size { + case 3: + self = unsafeBitCast(Allocator3(flag: isPressed), to: Self.self) + case 24: + self = unsafeBitCast(Allocator24(flag: isPressed), to: Self.self) + default: + fatalError(MemoryLayout.actualSize()) + } } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public extension PrimitiveButtonStyleConfiguration { - private struct Allocator { + private struct Allocator16 { + let onTrigger: () -> Void + } + private struct Allocator24 { + let buffer: Int8 = 0 let onTrigger: () -> Void } init(onTrigger: @escaping () -> Void) { - self = unsafeBitCast(Allocator(onTrigger: onTrigger), to: Self.self) + switch MemoryLayout.size { + case 16: + self = unsafeBitCast(Allocator16(onTrigger: onTrigger), to: Self.self) + case 24: + self = unsafeBitCast(Allocator24(onTrigger: onTrigger), to: Self.self) + default: + fatalError(MemoryLayout.actualSize()) + } + } +} + +internal extension MemoryLayout { + static func actualSize() -> String { + fatalError("New size of \(String(describing: type(of: T.self))) is \(Self.size)") } } diff --git a/Sources/ViewInspector/SwiftUI/Color.swift b/Sources/ViewInspector/SwiftUI/Color.swift index 9ee94cd2..df9d08d5 100644 --- a/Sources/ViewInspector/SwiftUI/Color.swift +++ b/Sources/ViewInspector/SwiftUI/Color.swift @@ -40,7 +40,7 @@ public extension InspectableView where View == ViewType.Color { func rgba() throws -> (red: Float, green: Float, blue: Float, alpha: Float) { let colorProvider = try Inspector.attribute(path: "provider|base", value: content.view) let providerName = Inspector.typeName(value: colorProvider) - if providerName == "_Resolved" { + if ["_Resolved", "Resolved"].contains(providerName) { let red = try Inspector.attribute(label: "linearRed", value: colorProvider, type: Float.self) let green = try Inspector.attribute(label: "linearGreen", value: colorProvider, type: Float.self) let blue = try Inspector.attribute(label: "linearBlue", value: colorProvider, type: Float.self) diff --git a/Sources/ViewInspector/SwiftUI/ColorPicker.swift b/Sources/ViewInspector/SwiftUI/ColorPicker.swift index 36e78de9..2ae2f103 100644 --- a/Sources/ViewInspector/SwiftUI/ColorPicker.swift +++ b/Sources/ViewInspector/SwiftUI/ColorPicker.swift @@ -46,7 +46,7 @@ public extension InspectableView where View == ViewType.ColorPicker { .asInspectableView(ofType: ViewType.ClassifiedView.self) } - @available(tvOS 14.0, *) + @available(tvOS 14.0, watchOS 7.0, *) func select(color: Color) throws { try guardIsResponsive() #if os(macOS) @@ -101,7 +101,7 @@ public extension ViewType.ColorPicker { #endif } - @available(tvOS 14.0, *) + @available(tvOS 14.0, watchOS 7.0, *) init(color: Color) { #if os(macOS) self.init(color: NSColor(color)) diff --git a/Sources/ViewInspector/SwiftUI/ConditionalContent.swift b/Sources/ViewInspector/SwiftUI/ConditionalContent.swift index 0c4dfbde..5f8b4f4a 100644 --- a/Sources/ViewInspector/SwiftUI/ConditionalContent.swift +++ b/Sources/ViewInspector/SwiftUI/ConditionalContent.swift @@ -12,7 +12,7 @@ extension ViewType.ConditionalContent: SingleViewContent { static func child(_ content: Content) throws -> Content { let storage = try Inspector.attribute(label: "storage", value: content.view) - let medium = content.medium.resettingViewModifiers() + let medium = content.medium if let trueContent = try? Inspector.attribute(label: "trueContent", value: storage) { return try Inspector.unwrap(view: trueContent, medium: medium) } diff --git a/Sources/ViewInspector/SwiftUI/ConfirmationDialog.swift b/Sources/ViewInspector/SwiftUI/ConfirmationDialog.swift new file mode 100644 index 00000000..05b4c6e1 --- /dev/null +++ b/Sources/ViewInspector/SwiftUI/ConfirmationDialog.swift @@ -0,0 +1,118 @@ +import SwiftUI + +// MARK: - Alert + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewType { + + struct ConfirmationDialog: KnownViewType { + public static var typePrefix: String = "ConfirmationDialogModifier" + public static func inspectionCall(typeName: String) -> String { + return "confirmationDialog(\(ViewType.indexPlaceholder))" + } + } +} + +// MARK: - Extraction + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +public extension InspectableView { + + func confirmationDialog(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.confirmationDialog(parent: self, index: index) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Content { + + func confirmationDialog(parent: UnwrappedView, index: Int?) throws -> InspectableView { + let modifier = try self.modifierAttribute( + modifierLookup: isConfirmationDialog(modifier:), path: "modifier", + type: Any.self, call: "confirmationDialog", index: index ?? 0) + let medium = self.medium.resettingViewModifiers() + let content = Content(modifier, medium: medium) + let call = ViewType.inspectionCall( + base: ViewType.ConfirmationDialog.inspectionCall(typeName: ""), index: index) + let view = try InspectableView( + content, parent: parent, call: call, index: index) + guard try view.isPresentedBinding().wrappedValue else { + throw InspectionError.viewNotFound(parent: "ConfirmationDialog") + } + return view + } + + private func isConfirmationDialog(modifier: Any) -> Bool { + guard let modifier = modifier as? ModifierNameProvider + else { return false } + return modifier.modifierType.contains(ViewType.ConfirmationDialog.typePrefix) + } +} + +// MARK: - Non Standard Children + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.ConfirmationDialog: SupplementaryChildren { + static func supplementaryChildren(_ parent: UnwrappedView) throws -> LazyGroup { + return .init(count: 3) { index in + let medium = parent.content.medium.resettingViewModifiers() + switch index { + case 0: + let view = try Inspector.attribute(path: "title", value: parent.content.view) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + return try InspectableView( + content, parent: parent, call: "title()") + case 1: + let view = try Inspector.attribute(path: "message", value: parent.content.view) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + return try InspectableView( + content, parent: parent, call: "message()") + default: + let view = try Inspector.attribute(path: "actions", value: parent.content.view) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + return try InspectableView( + content, parent: parent, call: "actions()") + } + } + } +} + +// MARK: - Custom Attributes + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +public extension InspectableView where View == ViewType.ConfirmationDialog { + + func title() throws -> InspectableView { + return try View.supplementaryChildren(self).element(at: 0) + .asInspectableView(ofType: ViewType.Text.self) + } + + func message() throws -> InspectableView { + return try View.supplementaryChildren(self).element(at: 1) + .asInspectableView(ofType: ViewType.ClassifiedView.self) + } + + func actions() throws -> InspectableView { + return try View.supplementaryChildren(self).element(at: 2) + .asInspectableView(ofType: ViewType.ClassifiedView.self) + } + + #if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 + func titleVisibility() throws -> Visibility { + return try Inspector.attribute( + label: "titleVisibility", value: content.view, type: Visibility.self) + } + #endif + + func dismiss() throws { + try isPresentedBinding().wrappedValue = false + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension InspectableView where View == ViewType.ConfirmationDialog { + func isPresentedBinding() throws -> Binding { + return try Inspector.attribute( + label: "isPresented", value: content.view, type: Binding.self) + } +} diff --git a/Sources/ViewInspector/SwiftUI/CustomView.swift b/Sources/ViewInspector/SwiftUI/CustomView.swift index 3dac5507..6071a8c0 100644 --- a/Sources/ViewInspector/SwiftUI/CustomView.swift +++ b/Sources/ViewInspector/SwiftUI/CustomView.swift @@ -135,7 +135,7 @@ public extension Inspectable where Self: NSViewControllerRepresentable { "Please use `.actualView().viewController()` for inspecting the contents of NSViewControllerRepresentable") } } -#else +#elseif os(iOS) || os(tvOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public extension UIViewRepresentable where Self: Inspectable { func uiView() throws -> UIViewType { @@ -165,4 +165,23 @@ public extension Inspectable where Self: UIViewControllerRepresentable { "Please use `.actualView().viewController()` for inspecting the contents of UIViewControllerRepresentable") } } +#elseif os(watchOS) + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +public extension WKInterfaceObjectRepresentable where Self: Inspectable { + func interfaceObject() throws -> WKInterfaceObjectType { + return try ViewHosting.lookup(Self.self) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +public extension Inspectable where Self: WKInterfaceObjectRepresentable { + func extractContent(environmentObjects: [AnyObject]) throws -> Any { + throw InspectionError.notSupported( + """ + Please use `.actualView().interfaceObject()` for inspecting \ + the contents of WKInterfaceObjectRepresentable + """) + } +} #endif diff --git a/Sources/ViewInspector/SwiftUI/CustomViewModifier.swift b/Sources/ViewInspector/SwiftUI/CustomViewModifier.swift index 10dc8b30..5b0b6c3d 100644 --- a/Sources/ViewInspector/SwiftUI/CustomViewModifier.swift +++ b/Sources/ViewInspector/SwiftUI/CustomViewModifier.swift @@ -66,7 +66,8 @@ internal extension Content { func unwrappedModifiedContent() throws -> Content { let view = try Inspector.attribute(label: "content", value: self.view) var medium: Content.Medium - if let modifier = self.view as? EnvironmentModifier { + if let modifier = self.view as? EnvironmentModifier, + modifier.qualifiesAsEnvironmentModifier() { if let value = try? modifier.value(), let object = try? Inspector.attribute(label: "some", value: value, type: AnyObject.self), !(object is NSObject) { diff --git a/Sources/ViewInspector/SwiftUI/EnvironmentReaderView.swift b/Sources/ViewInspector/SwiftUI/EnvironmentReaderView.swift index 4b899060..b8832472 100644 --- a/Sources/ViewInspector/SwiftUI/EnvironmentReaderView.swift +++ b/Sources/ViewInspector/SwiftUI/EnvironmentReaderView.swift @@ -17,13 +17,19 @@ extension ViewType.EnvironmentReaderView: SingleViewContent { // MARK: - Extraction from SingleViewContent parent -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) +@available(macOS, unavailable) +@available(watchOS, unavailable) public extension InspectableView where View: SingleViewContent { + @available(iOS, deprecated: 100000.0, message: "Please use `toolbar()` for inspecting `navigationBarItems`") + @available(tvOS, deprecated: 100000.0, message: "Please use `toolbar()` for inspecting `navigationBarItems`") func navigationBarItems() throws -> InspectableView { return try navigationBarItems(AnyView.self) } + @available(iOS, deprecated: 100000.0, message: "Please use `toolbar()` for inspecting `navigationBarItems`") + @available(tvOS, deprecated: 100000.0, message: "Please use `toolbar()` for inspecting `navigationBarItems`") func navigationBarItems(_ viewType: V.Type) throws -> InspectableView where V: SwiftUI.View { return try navigationBarItems(viewType: viewType, content: try child()) diff --git a/Sources/ViewInspector/SwiftUI/Gesture.swift b/Sources/ViewInspector/SwiftUI/Gesture.swift index 6cc04a26..18c0f1c7 100644 --- a/Sources/ViewInspector/SwiftUI/Gesture.swift +++ b/Sources/ViewInspector/SwiftUI/Gesture.swift @@ -60,10 +60,12 @@ extension LongPressGesture: Inspectable {} @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) extension MagnificationGesture: Inspectable {} @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) extension RotationGesture: Inspectable {} @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) @@ -419,6 +421,7 @@ public extension LongPressGesture.Value { @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension MagnificationGesture.Value { private struct Allocator { @@ -435,6 +438,7 @@ public extension MagnificationGesture.Value { @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension RotationGesture.Value { private struct Allocator { diff --git a/Sources/ViewInspector/SwiftUI/GroupBox.swift b/Sources/ViewInspector/SwiftUI/GroupBox.swift index c92ce317..057542a2 100644 --- a/Sources/ViewInspector/SwiftUI/GroupBox.swift +++ b/Sources/ViewInspector/SwiftUI/GroupBox.swift @@ -74,8 +74,10 @@ public extension InspectableView { // MARK: - GroupBoxStyle inspection +#if os(iOS) || os(macOS) @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension GroupBoxStyle { func inspect() throws -> InspectableView { let config = GroupBoxStyleConfiguration() @@ -88,9 +90,11 @@ public extension GroupBoxStyle { @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private extension GroupBoxStyleConfiguration { private struct Allocator { } init() { self = unsafeBitCast(Allocator(), to: Self.self) } } +#endif diff --git a/Sources/ViewInspector/SwiftUI/Image.swift b/Sources/ViewInspector/SwiftUI/Image.swift index 3eb425ca..17493d58 100644 --- a/Sources/ViewInspector/SwiftUI/Image.swift +++ b/Sources/ViewInspector/SwiftUI/Image.swift @@ -79,7 +79,7 @@ public extension SwiftUI.Image { .attribute(label: "name", value: rawImage(), type: String.self) } - #if os(iOS) || os(tvOS) + #if !os(macOS) func uiImage() throws -> UIImage { return try Inspector.cast(value: try rawImage(), type: UIImage.self) } diff --git a/Sources/ViewInspector/SwiftUI/Label.swift b/Sources/ViewInspector/SwiftUI/Label.swift index bd32b23f..0da46c2b 100644 --- a/Sources/ViewInspector/SwiftUI/Label.swift +++ b/Sources/ViewInspector/SwiftUI/Label.swift @@ -73,7 +73,8 @@ public extension InspectableView { func labelStyle() throws -> Any { let modifier = try self.modifier({ modifier -> Bool in - return modifier.modifierType.hasPrefix("LabelStyleModifier") + return ["LabelStyleModifier", "LabelStyleWritingModifier"] + .contains(where: { modifier.modifierType.hasPrefix($0) }) }, call: "labelStyle") return try Inspector.attribute(path: "modifier|style", value: modifier) } @@ -81,7 +82,7 @@ public extension InspectableView { // MARK: - LabelStyle inspection -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension LabelStyle { func inspect() throws -> InspectableView { let config = LabelStyleConfiguration() @@ -92,7 +93,7 @@ public extension LabelStyle { // MARK: - Style Configuration initializer -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) private extension LabelStyleConfiguration { struct Allocator { } init() { diff --git a/Sources/ViewInspector/SwiftUI/LazyHGrid.swift b/Sources/ViewInspector/SwiftUI/LazyHGrid.swift index 8b835de8..3ac3c55c 100644 --- a/Sources/ViewInspector/SwiftUI/LazyHGrid.swift +++ b/Sources/ViewInspector/SwiftUI/LazyHGrid.swift @@ -41,7 +41,7 @@ extension ViewType.LazyHGrid: MultipleViewContent { // MARK: - Custom Attributes -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension InspectableView where View == ViewType.LazyHGrid { func alignment() throws -> VerticalAlignment { @@ -69,7 +69,7 @@ public extension InspectableView where View == ViewType.LazyHGrid { } } -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension GridItem: Equatable { public static func == (lhs: GridItem, rhs: GridItem) -> Bool { return lhs.size == rhs.size @@ -78,7 +78,7 @@ extension GridItem: Equatable { } } -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension GridItem.Size: Equatable { public static func == (lhs: GridItem.Size, rhs: GridItem.Size) -> Bool { switch (lhs, rhs) { diff --git a/Sources/ViewInspector/SwiftUI/LazyHStack.swift b/Sources/ViewInspector/SwiftUI/LazyHStack.swift index 04231bc0..74b928d5 100644 --- a/Sources/ViewInspector/SwiftUI/LazyHStack.swift +++ b/Sources/ViewInspector/SwiftUI/LazyHStack.swift @@ -41,7 +41,7 @@ extension ViewType.LazyHStack: MultipleViewContent { // MARK: - Custom Attributes -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension InspectableView where View == ViewType.LazyHStack { func alignment() throws -> VerticalAlignment { diff --git a/Sources/ViewInspector/SwiftUI/LazyVGrid.swift b/Sources/ViewInspector/SwiftUI/LazyVGrid.swift index 6a1adce5..6bfb895f 100644 --- a/Sources/ViewInspector/SwiftUI/LazyVGrid.swift +++ b/Sources/ViewInspector/SwiftUI/LazyVGrid.swift @@ -41,7 +41,7 @@ extension ViewType.LazyVGrid: MultipleViewContent { // MARK: - Custom Attributes -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension InspectableView where View == ViewType.LazyVGrid { func alignment() throws -> HorizontalAlignment { diff --git a/Sources/ViewInspector/SwiftUI/LazyVStack.swift b/Sources/ViewInspector/SwiftUI/LazyVStack.swift index b619bd20..909fef0a 100644 --- a/Sources/ViewInspector/SwiftUI/LazyVStack.swift +++ b/Sources/ViewInspector/SwiftUI/LazyVStack.swift @@ -41,7 +41,7 @@ extension ViewType.LazyVStack: MultipleViewContent { // MARK: - Custom Attributes -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension InspectableView where View == ViewType.LazyVStack { func alignment() throws -> HorizontalAlignment { diff --git a/Sources/ViewInspector/SwiftUI/List.swift b/Sources/ViewInspector/SwiftUI/List.swift index 48bdcd68..041dd93f 100644 --- a/Sources/ViewInspector/SwiftUI/List.swift +++ b/Sources/ViewInspector/SwiftUI/List.swift @@ -50,8 +50,8 @@ public extension InspectableView { path: "modifier|value|some", type: EdgeInsets.self, call: "listRowInsets") } - func listRowBackground() throws -> InspectableView { - return try contentForModifierLookup.listRowBackground(parent: self) + func listRowBackground(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.listRowBackground(parent: self, index: index) } func listStyle() throws -> Any { @@ -65,11 +65,15 @@ public extension InspectableView { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func listRowBackground(parent: UnwrappedView) throws -> InspectableView { + func listRowBackground(parent: UnwrappedView, index: Int?) throws -> InspectableView { let view = try modifierAttribute( modifierName: "_TraitWritingModifier", - path: "modifier|value|some|storage|view", type: Any.self, call: "listRowBackground") + path: "modifier|value|some|storage|view", type: Any.self, + call: "listRowBackground", index: index ?? 0) let medium = self.medium.resettingViewModifiers() - return try .init(try Inspector.unwrap(content: Content(view, medium: medium)), parent: parent) + let content = try Inspector.unwrap(content: Content(view, medium: medium)) + let call = ViewType.inspectionCall( + base: "listRowBackground(\(ViewType.indexPlaceholder))", index: index) + return try .init(content, parent: parent, call: call, index: index) } } diff --git a/Sources/ViewInspector/SwiftUI/Map.swift b/Sources/ViewInspector/SwiftUI/Map.swift index 0f19d2c4..74f62402 100644 --- a/Sources/ViewInspector/SwiftUI/Map.swift +++ b/Sources/ViewInspector/SwiftUI/Map.swift @@ -33,7 +33,7 @@ public extension InspectableView where View: MultipleViewContent { // MARK: - Custom Attributes -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) public extension InspectableView where View == ViewType.Map { func coordinateRegion() throws -> MKCoordinateRegion { @@ -92,7 +92,7 @@ public extension InspectableView where View == ViewType.Map { } } -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) private extension InspectableView where View == ViewType.Map { func coordinateRegionBinding() throws -> Binding { @@ -119,7 +119,7 @@ internal protocol IdentifiableItemsContainer { func contains(_ item: T) -> Bool } -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) extension _DefaultAnnotatedMapContent: IdentifiableItemsContainer { func contains(_ item: T) -> Bool { guard let item = item as? Items.Element, diff --git a/Sources/ViewInspector/SwiftUI/MapAnnotation.swift b/Sources/ViewInspector/SwiftUI/MapAnnotation.swift index 26ca181b..42de218a 100644 --- a/Sources/ViewInspector/SwiftUI/MapAnnotation.swift +++ b/Sources/ViewInspector/SwiftUI/MapAnnotation.swift @@ -41,7 +41,7 @@ public extension InspectableView where View == ViewType.MapAnnotation { // MARK: - SwiftUI MapAnnotation -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) public extension MapAnnotation { func coordinate() throws -> CLLocationCoordinate2D { @@ -63,7 +63,7 @@ public extension MapAnnotation { // MARK: - SwiftUI MapMarker -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) public extension MapMarker { func coordinate() throws -> CLLocationCoordinate2D { @@ -79,7 +79,7 @@ public extension MapMarker { // MARK: - SwiftUI MapPin -@available(iOS 14.0, tvOS 14.0, macOS 11.0, *) +@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) public extension MapPin { func coordinate() throws -> CLLocationCoordinate2D { diff --git a/Sources/ViewInspector/SwiftUI/Menu.swift b/Sources/ViewInspector/SwiftUI/Menu.swift index 55f53e43..72a98ff1 100644 --- a/Sources/ViewInspector/SwiftUI/Menu.swift +++ b/Sources/ViewInspector/SwiftUI/Menu.swift @@ -74,8 +74,10 @@ public extension InspectableView { // MARK: - MenuStyle inspection +#if os(iOS) || os(macOS) @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension MenuStyle { func inspect() throws -> InspectableView { let config = MenuStyleConfiguration() @@ -88,9 +90,21 @@ public extension MenuStyle { @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private extension MenuStyleConfiguration { - struct Allocator { } + struct Allocator0 { } + struct Allocator16 { + let data: (Int64, Int64) = (0, 0) + } init() { - self = unsafeBitCast(Allocator(), to: Self.self) + switch MemoryLayout.size { + case 0: + self = unsafeBitCast(Allocator0(), to: Self.self) + case 16: + self = unsafeBitCast(Allocator16(), to: Self.self) + default: + fatalError(MemoryLayout.actualSize()) + } } } +#endif diff --git a/Sources/ViewInspector/SwiftUI/OutlineGroup.swift b/Sources/ViewInspector/SwiftUI/OutlineGroup.swift index d2800f21..6d2c4e03 100644 --- a/Sources/ViewInspector/SwiftUI/OutlineGroup.swift +++ b/Sources/ViewInspector/SwiftUI/OutlineGroup.swift @@ -58,8 +58,10 @@ private protocol LeafContentProvider { func view(_ element: Any) throws -> Any } +#if os(iOS) || os(macOS) @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) extension OutlineGroup: LeafContentProvider { func view(_ element: Any) throws -> Any { guard let data = element as? Data.Element else { @@ -71,3 +73,4 @@ extension OutlineGroup: LeafContentProvider { return builder(data) } } +#endif diff --git a/Sources/ViewInspector/SwiftUI/Overlay.swift b/Sources/ViewInspector/SwiftUI/Overlay.swift index 5d897fef..bd29650a 100644 --- a/Sources/ViewInspector/SwiftUI/Overlay.swift +++ b/Sources/ViewInspector/SwiftUI/Overlay.swift @@ -14,12 +14,12 @@ public extension ViewType { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public extension InspectableView { - func overlay() throws -> InspectableView { - return try contentForModifierLookup.overlay(parent: self) + func overlay(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.overlay(parent: self, index: index) } - func background() throws -> InspectableView { - return try contentForModifierLookup.background(parent: self) + func background(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.background(parent: self, index: index) } } @@ -44,30 +44,32 @@ extension ViewType.Overlay: MultipleViewContent { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func overlay(parent: UnwrappedView) throws -> InspectableView { + func overlay(parent: UnwrappedView, index: Int?) throws -> InspectableView { let modifier = try self.modifier({ modifier -> Bool in return modifier.modifierType.contains("_OverlayModifier") - }, call: "overlay") + }, call: "overlay", index: index ?? 0) let rootView = try Inspector.attribute(path: "modifier|overlay", value: modifier) let alignment = try Inspector.attribute(path: "modifier|alignment", value: modifier, type: Alignment.self) let overlayParams = ViewType.Overlay.Params(alignment: alignment) let medium = self.medium.resettingViewModifiers() .appending(viewModifier: overlayParams) let content = try Inspector.unwrap(content: Content(rootView, medium: medium)) - return try .init(content, parent: parent, call: "overlay()") + let call = ViewType.inspectionCall(base: "overlay(\(ViewType.indexPlaceholder))", index: index) + return try .init(content, parent: parent, call: call, index: index) } - func background(parent: UnwrappedView) throws -> InspectableView { + func background(parent: UnwrappedView, index: Int?) throws -> InspectableView { let modifier = try self.modifier({ modifier -> Bool in return modifier.modifierType.contains("_BackgroundModifier") - }, call: "background") + }, call: "background", index: index ?? 0) let rootView = try Inspector.attribute(path: "modifier|background", value: modifier) let alignment = try Inspector.attribute(path: "modifier|alignment", value: modifier, type: Alignment.self) let overlayParams = ViewType.Overlay.Params(alignment: alignment) let medium = self.medium.resettingViewModifiers() .appending(viewModifier: overlayParams) let content = try Inspector.unwrap(content: Content(rootView, medium: medium)) - return try .init(content, parent: parent, call: "background()") + let call = ViewType.inspectionCall(base: "background(\(ViewType.indexPlaceholder))", index: index) + return try .init(content, parent: parent, call: call, index: index) } } diff --git a/Sources/ViewInspector/SwiftUI/Popover.swift b/Sources/ViewInspector/SwiftUI/Popover.swift index 53fdf530..57354b6a 100644 --- a/Sources/ViewInspector/SwiftUI/Popover.swift +++ b/Sources/ViewInspector/SwiftUI/Popover.swift @@ -4,32 +4,85 @@ import SwiftUI public extension ViewType { struct Popover: KnownViewType { - public static var typePrefix: String = "" - public static var isTransitive: Bool { true } + public static var typePrefix: String = ViewType.PopupContainer.typePrefix + public static var namespacedPrefixes: [String] { [typePrefix] } + public static func inspectionCall(typeName: String) -> String { + return "popover(\(ViewType.indexPlaceholder))" + } + internal static var standardModifierName: String { + if #available(iOS 14.2, macOS 11.0, *) { + return "PopoverPresentationModifier" + } else { + return "_AnchorWritingModifier, Key>" + } + } + } +} + +// MARK: - Content Extraction + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Popover: SingleViewContent { + + public static func child(_ content: Content) throws -> Content { + let view = try Inspector.attribute(label: "popup", value: content.view) + let medium = content.medium.resettingViewModifiers() + return try Inspector.unwrap(view: view, medium: medium) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Popover: MultipleViewContent { + + public static func children(_ content: Content) throws -> LazyGroup { + let view = try Inspector.attribute(label: "popup", value: content.view) + let medium = content.medium.resettingViewModifiers() + return try Inspector.viewsInContainer(view: view, medium: medium) } } // MARK: - Extraction -@available(iOS 14.2, macOS 11.0, *) +@available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension InspectableView { - func popover() throws -> InspectableView { - return try contentForModifierLookup.popover(parent: self) + func popover(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.popover(parent: self, index: index) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func popover(parent: UnwrappedView) throws -> InspectableView { - let modifier = try modifierAttribute( - modifierName: "PopoverPresentationModifier", path: "modifier", - type: Any.self, call: "popover") - let medium = self.medium.resettingViewModifiers() - return try .init(try Inspector.unwrap(content: Content(modifier, medium: medium)), - parent: parent, call: "popover()") + func popover(parent: UnwrappedView, index: Int?) throws -> InspectableView { + return try popup(parent: parent, index: index, + modifierPredicate: isPopoverBuilder(modifier:), + standardPredicate: standardPopoverModifier) + } + + func standardPopoverModifier(_ name: String = "Popover") throws -> Any { + return try modifierAttribute( + modifierName: ViewType.Popover.standardModifierName, path: "modifier", + type: Any.self, call: name.firstLetterLowercased) + } + + func popoversForSearch() -> [ViewSearch.ModifierIdentity] { + let count = medium.viewModifiers + .filter(isPopoverBuilder(modifier:)) + .count + return Array(0.. Bool { + let modifier = try? Inspector.attribute( + label: "modifier", value: modifier, type: BasePopupPresenter.self) + return modifier?.isPopoverPresenter == true } } @@ -37,55 +90,56 @@ internal extension Content { @available(iOS 14.2, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) public extension InspectableView where View == ViewType.Popover { - func contentView() throws -> InspectableView { - return try contentView(EmptyView.self) - } - - func contentView(_ viewType: T.Type) throws -> InspectableView { - - typealias Closure = () -> T - let closure = try Inspector.attribute(label: "popoverContent", value: content.view) - let closureDesc = Inspector.typeName(value: closure) - - let expectedViewType = closureDesc.components(separatedBy: "() -> ").last ?? "" - guard Inspector.typeName(type: viewType) == expectedViewType else { - throw InspectionError.notSupported( - "Please substitute '\(expectedViewType).self' as the parameter for 'contentView()' inspection call") - } - guard let typedClosure = withUnsafeBytes(of: closure, { - $0.bindMemory(to: Closure.self).first - }) else { throw InspectionError.typeMismatch(closure, Closure.self) } - let view = typedClosure() - let medium = content.medium.resettingViewModifiers() - return try .init(try Inspector.unwrap(content: Content(view, medium: medium)), parent: self) - } - func arrowEdge() throws -> Edge { - return try Inspector.attribute(label: "arrowEdge", value: content.view, type: Edge.self) + let popover = try Inspector.cast(value: content.view, type: ViewType.PopupContainer.self) + let modifier = try popover.presenter.content().standardPopoverModifier() + return try Inspector.attribute( + label: "arrowEdge", value: modifier, type: Edge.self) } func attachmentAnchor() throws -> PopoverAttachmentAnchor { - return try Inspector.attribute(label: "attachmentAnchor", value: content.view, - type: PopoverAttachmentAnchor.self) + let popover = try Inspector.cast(value: content.view, type: ViewType.PopupContainer.self) + let modifier = try popover.presenter.content().standardPopoverModifier() + return try Inspector.attribute( + label: "attachmentAnchor", value: modifier, type: PopoverAttachmentAnchor.self) } +} + +@available(iOS 13.0, macOS 10.15, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +public extension InspectableView where View == ViewType.Popover { - func isPresented() throws -> Bool { - return try isPresentedBinding().wrappedValue + func dismiss() throws { + let container = try Inspector.cast(value: content.view, type: ViewType.PopupContainer.self) + container.presenter.dismissPopup() } +} + +// MARK: - Deprecated: + +@available(iOS 13.0, macOS 10.15, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +public extension InspectableView where View == ViewType.Popover { - func dismiss() throws { - typealias OnDismiss = () -> Void - let onDismiss = try Inspector.attribute( - label: "onDismiss", value: content.view, type: OnDismiss.self) - onDismiss() - try isPresentedBinding().wrappedValue = false + @available(*, deprecated, message: "Simply remove `contentView()` from the inspection chain") + func contentView() throws -> InspectableView { + return try contentView(EmptyView.self) } - private func isPresentedBinding() throws -> Binding { - return try Inspector.attribute( - label: "_isPresented", value: content.view, type: Binding.self) + @available(*, deprecated, message: "Simply remove `contentView()` from the inspection chain") + func contentView(_ viewType: T.Type) throws -> InspectableView { + let content = try ViewType.Popover.child(self.content) + return try .init(content, parent: self, index: nil) + } + + @available(*, deprecated, message: "Use `popover` inspection call - it throws if Popover is not presented") + func isPresented() throws -> Bool { + return true } } diff --git a/Sources/ViewInspector/SwiftUI/ProgressView.swift b/Sources/ViewInspector/SwiftUI/ProgressView.swift index 8b92ca89..c60e5640 100644 --- a/Sources/ViewInspector/SwiftUI/ProgressView.swift +++ b/Sources/ViewInspector/SwiftUI/ProgressView.swift @@ -99,7 +99,7 @@ public extension InspectableView { // MARK: - ProgressViewStyle inspection -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public extension ProgressViewStyle { func inspect(fractionCompleted: Double? = nil) throws -> InspectableView { let config = ProgressViewStyleConfiguration(fractionCompleted: fractionCompleted) @@ -110,7 +110,7 @@ public extension ProgressViewStyle { // MARK: - Style Configuration initializer -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) internal extension ProgressViewStyleConfiguration { private struct Allocator { let fractionCompleted: Double? diff --git a/Sources/ViewInspector/SwiftUI/SafeAreaInset.swift b/Sources/ViewInspector/SwiftUI/SafeAreaInset.swift new file mode 100644 index 00000000..1f700960 --- /dev/null +++ b/Sources/ViewInspector/SwiftUI/SafeAreaInset.swift @@ -0,0 +1,87 @@ +import SwiftUI + +// MARK: - Alert + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewType { + + struct SafeAreaInset: KnownViewType { + public static var typePrefix: String = "_InsetViewModifier" + public static func inspectionCall(typeName: String) -> String { + return "safeAreaInset(\(ViewType.indexPlaceholder))" + } + } +} + +// MARK: - Extraction + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +public extension InspectableView { + + func safeAreaInset(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.safeAreaInset(parent: self, index: index) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Content { + + func safeAreaInset(parent: UnwrappedView, index: Int?) throws -> InspectableView { + let modifier = try self.modifierAttribute( + modifierLookup: isSafeAreaInset(modifier:), path: "modifier", + type: Any.self, call: "safeAreaInset", index: index ?? 0) + let medium = self.medium.resettingViewModifiers() + let content = Content(modifier, medium: medium) + let call = ViewType.inspectionCall( + base: ViewType.SafeAreaInset.inspectionCall(typeName: ""), index: index) + return try .init(content, parent: parent, call: call, index: index) + } + + private func isSafeAreaInset(modifier: Any) -> Bool { + guard let modifier = modifier as? ModifierNameProvider + else { return false } + return modifier.modifierType.contains(ViewType.SafeAreaInset.typePrefix) + } +} + +// MARK: - Content Extraction + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.SafeAreaInset: SingleViewContent { + + public static func child(_ content: Content) throws -> Content { + let view = try Inspector.attribute(path: "content", value: content.view) + let medium = content.medium.resettingViewModifiers() + return try Inspector.unwrap(view: view, medium: medium) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.SafeAreaInset: MultipleViewContent { + + public static func children(_ content: Content) throws -> LazyGroup { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.viewsInContainer(view: view, medium: content.medium) + } +} + +// MARK: - Custom Attributes + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +public extension InspectableView where View == ViewType.SafeAreaInset { + + func regions() throws -> SafeAreaRegions { + return try Inspector.attribute( + path: "properties|regions", value: content.view, type: SafeAreaRegions.self) + } + + func spacing() throws -> CGFloat? { + return try Inspector.attribute( + path: "properties|spacing", value: content.view, type: CGFloat?.self) + } + + func edge() throws -> Edge { + return try Inspector.attribute( + path: "properties|edge", value: content.view, type: Edge.self) + } +} diff --git a/Sources/ViewInspector/SwiftUI/ScrollViewReader.swift b/Sources/ViewInspector/SwiftUI/ScrollViewReader.swift index ffba3d81..6b6a4805 100644 --- a/Sources/ViewInspector/SwiftUI/ScrollViewReader.swift +++ b/Sources/ViewInspector/SwiftUI/ScrollViewReader.swift @@ -46,7 +46,7 @@ private protocol ScrollViewReaderContentProvider { func view() throws -> Any } -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension ScrollViewReader: ScrollViewReaderContentProvider { func view() throws -> Any { typealias Builder = (ScrollViewProxy) -> Content @@ -56,7 +56,7 @@ extension ScrollViewReader: ScrollViewReaderContentProvider { } } -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) private extension ScrollViewProxy { struct Allocator8 { let data: Int64 = 0 diff --git a/Sources/ViewInspector/SwiftUI/SecureField.swift b/Sources/ViewInspector/SwiftUI/SecureField.swift index a8526704..073325f5 100644 --- a/Sources/ViewInspector/SwiftUI/SecureField.swift +++ b/Sources/ViewInspector/SwiftUI/SecureField.swift @@ -53,14 +53,24 @@ public extension InspectableView where View == ViewType.SecureField { } private func inputBinding() throws -> Binding { + if let binding = try? Inspector.attribute( + label: "text", value: content.view, type: Binding.self) { + return binding + } return try Inspector.attribute( - label: "text", value: content.view, type: Binding.self) + label: "_text", value: content.view, type: Binding.self) } func callOnCommit() throws { typealias Callback = () -> Void - let callback = try Inspector - .attribute(label: "onCommit", value: content.view, type: Callback.self) + let callback: Callback = try { + if let value = try? Inspector + .attribute(label: "onCommit", value: content.view, type: Callback.self) { + return value + } + return try Inspector + .attribute(path: "deprecatedActions|some|commit", value: content.view, type: Callback.self) + }() callback() } } diff --git a/Sources/ViewInspector/SwiftUI/Shape.swift b/Sources/ViewInspector/SwiftUI/Shape.swift index fee25c71..7fdd22a7 100644 --- a/Sources/ViewInspector/SwiftUI/Shape.swift +++ b/Sources/ViewInspector/SwiftUI/Shape.swift @@ -5,6 +5,9 @@ public extension ViewType { struct Shape: KnownViewType { public static var typePrefix: String = "" + public static func inspectionCall(typeName: String) -> String { + return "shape(\(ViewType.indexPlaceholder))" + } } } @@ -15,7 +18,7 @@ public extension InspectableView where View: SingleViewContent { func shape() throws -> InspectableView { let content = try child() - try guardShapeIsInspectable(content.view) + try content.throwIfNotShape() return try .init(content, parent: self) } } @@ -27,7 +30,7 @@ public extension InspectableView where View: MultipleViewContent { func shape(_ index: Int) throws -> InspectableView { let content = try child(at: index) - try guardShapeIsInspectable(content.view) + try content.throwIfNotShape() return try .init(content, parent: self, index: index) } } @@ -97,12 +100,6 @@ public extension InspectableView where View == ViewType.Shape { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private extension InspectableView { - func guardShapeIsInspectable(_ view: Any) throws { - guard view is InspectableShape || Inspector.typeName(value: view) == "_Inset" else { - throw InspectionError.typeMismatch(view, InspectableShape.self) - } - } - func shapeAttribute(_ view: Any, _ shapeType: String, _ label: String, _ attributeType: T.Type ) throws -> T { let shape = try lookupShape(view, typeName: shapeType, label: label) @@ -122,6 +119,24 @@ private extension InspectableView { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Content { + var isShape: Bool { + do { + try throwIfNotShape() + return true + } catch { + return false + } + } + + fileprivate func throwIfNotShape() throws { + guard view is InspectableShape || Inspector.typeName(value: view) == "_Inset" else { + throw InspectionError.typeMismatch(view, InspectableShape.self) + } + } +} + // MARK: - InspectableShape @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) diff --git a/Sources/ViewInspector/SwiftUI/Sheet.swift b/Sources/ViewInspector/SwiftUI/Sheet.swift index cc9f9f1e..5119d4ca 100644 --- a/Sources/ViewInspector/SwiftUI/Sheet.swift +++ b/Sources/ViewInspector/SwiftUI/Sheet.swift @@ -6,14 +6,13 @@ import SwiftUI public extension ViewType { struct Sheet: KnownViewType { - public static var typePrefix: String = "ViewType.Sheet.Container" - public static var namespacedPrefixes: [String] { - return ["ViewInspector." + typePrefix] - } + public static var typePrefix: String = ViewType.PopupContainer.typePrefix + public static var namespacedPrefixes: [String] { [typePrefix] } public static func inspectionCall(typeName: String) -> String { - return "sheet(\(ViewType.indexPlaceholder))" + return "\(typeName.firstLetterLowercased)(\(ViewType.indexPlaceholder))" } } + typealias FullScreenCover = Sheet } // MARK: - Content Extraction @@ -22,7 +21,7 @@ public extension ViewType { extension ViewType.Sheet: SingleViewContent { public static func child(_ content: Content) throws -> Content { - let view = try Inspector.attribute(label: "view", value: content.view) + let view = try Inspector.attribute(label: "popup", value: content.view) let medium = content.medium.resettingViewModifiers() return try Inspector.unwrap(view: view, medium: medium) } @@ -32,7 +31,7 @@ extension ViewType.Sheet: SingleViewContent { extension ViewType.Sheet: MultipleViewContent { public static func children(_ content: Content) throws -> LazyGroup { - let view = try Inspector.attribute(label: "view", value: content.view) + let view = try Inspector.attribute(label: "popup", value: content.view) let medium = content.medium.resettingViewModifiers() return try Inspector.viewsInContainer(view: view, medium: medium) } @@ -48,36 +47,34 @@ public extension InspectableView { } } +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(macOS, unavailable) +public extension InspectableView { + + func fullScreenCover(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.sheet(parent: self, index: index, name: "FullScreenCover") + } +} + @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func sheet(parent: UnwrappedView, index: Int?) throws -> InspectableView { - guard let sheetBuilder = try? self.modifierAttribute( - modifierLookup: { isSheetBuilder(modifier: $0) }, path: "modifier", - type: SheetBuilder.self, call: "", index: index ?? 0) - else { - _ = try self.modifier({ - $0.modifierType == "IdentifiedPreferenceTransformModifier" - || $0.modifierType.contains("SheetPresentationModifier") - }, call: "sheet") - throw InspectionError.notSupported( - """ - Please refer to the Guide for inspecting the Sheet: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#sheet - """) - } - let view = try sheetBuilder.buildSheet() - let container = ViewType.Sheet.Container(view: view, builder: sheetBuilder) - let medium = self.medium.resettingViewModifiers() - let content = Content(container, medium: medium) - let call = ViewType.inspectionCall( - base: ViewType.Sheet.inspectionCall(typeName: ""), index: index) - return try .init(content, parent: parent, call: call, index: index) + func sheet(parent: UnwrappedView, index: Int?, name: String = "Sheet") throws -> InspectableView { + return try popup(parent: parent, index: index, name: name, + modifierPredicate: isSheetBuilder(modifier:), + standardPredicate: standardSheetModifier) + } + + func standardSheetModifier(_ name: String = "Sheet") throws -> Any { + return try self.modifier({ + $0.modifierType == "IdentifiedPreferenceTransformModifier" + || $0.modifierType.contains("SheetPresentationModifier") + }, call: name.firstLetterLowercased) } func sheetsForSearch() -> [ViewSearch.ModifierIdentity] { let count = medium.viewModifiers - .compactMap { isSheetBuilder(modifier: $0) } + .filter(isSheetBuilder(modifier:)) .count return Array(0.. Bool { - return (try? Inspector.attribute( - label: "modifier", value: modifier, type: SheetBuilder.self)) != nil - } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -internal extension ViewType.Sheet { - struct Container: CustomViewIdentityMapping { - let view: Any - let builder: SheetBuilder - - var viewTypeForSearch: KnownViewType.Type { ViewType.Sheet.self } + let modifier = try? Inspector.attribute( + label: "modifier", value: modifier, type: BasePopupPresenter.self) + return modifier?.isSheetPresenter == true } } @@ -107,59 +95,13 @@ internal extension ViewType.Sheet { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) public extension InspectableView where View == ViewType.Sheet { - func callOnDismiss() throws { - let sheet = try Inspector.cast(value: content.view, type: ViewType.Sheet.Container.self) - sheet.builder.dismissPopup() - } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol SheetBuilder: SystemPopupPresenter { - var onDismiss: (() -> Void)? { get } - func buildSheet() throws -> Any -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol SheetProvider: SheetBuilder { - var isPresented: Binding { get } - var sheetBuilder: () -> Any { get } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public protocol SheetItemProvider: SheetBuilder { - associatedtype Item: Identifiable - var item: Binding { get } - var sheetBuilder: (Item) -> Any { get } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public extension SheetProvider { - - func buildSheet() throws -> Any { - guard isPresented.wrappedValue else { - throw InspectionError.viewNotFound(parent: "Sheet") - } - return sheetBuilder() + func dismiss() throws { + let container = try Inspector.cast(value: content.view, type: ViewType.PopupContainer.self) + container.presenter.dismissPopup() } - func dismissPopup() { - isPresented.wrappedValue = false - onDismiss?() - } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -public extension SheetItemProvider { - - func buildSheet() throws -> Any { - guard let value = item.wrappedValue else { - throw InspectionError.viewNotFound(parent: "Sheet") - } - return sheetBuilder(value) - } - - func dismissPopup() { - item.wrappedValue = nil - onDismiss?() + @available(*, deprecated, renamed: "dismiss") + func callOnDismiss() throws { + try dismiss() } } diff --git a/Sources/ViewInspector/SwiftUI/StyleConfiguration.swift b/Sources/ViewInspector/SwiftUI/StyleConfiguration.swift index 2c8ab7ce..96e84ad3 100644 --- a/Sources/ViewInspector/SwiftUI/StyleConfiguration.swift +++ b/Sources/ViewInspector/SwiftUI/StyleConfiguration.swift @@ -16,9 +16,9 @@ public extension ViewType.StyleConfiguration { ButtonStyleConfiguration.Label.self, ToggleStyleConfiguration.Label.self ] - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { types.append(ProgressViewStyleConfiguration.Label.self) - #if !os(tvOS) + #if os(iOS) || os(macOS) types.append(GroupBoxStyleConfiguration.Label.self) types.append(MenuStyleConfiguration.Label.self) #endif @@ -38,7 +38,7 @@ public extension ViewType.StyleConfiguration { public static var namespacedPrefixes: [String] { var types: [Any.Type] = [] if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { - #if !os(tvOS) + #if os(iOS) || os(macOS) types.append(GroupBoxStyleConfiguration.Content.self) types.append(MenuStyleConfiguration.Content.self) #endif @@ -57,7 +57,7 @@ public extension ViewType.StyleConfiguration { public static var namespacedPrefixes: [String] { var types: [Any.Type] = [] - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { types.append(LabelStyleConfiguration.Title.self) } return types @@ -74,7 +74,7 @@ public extension ViewType.StyleConfiguration { public static var namespacedPrefixes: [String] { var types: [Any.Type] = [] - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { types.append(LabelStyleConfiguration.Icon.self) } return types @@ -91,7 +91,7 @@ public extension ViewType.StyleConfiguration { public static var namespacedPrefixes: [String] { var types: [Any.Type] = [] - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { types.append(ProgressViewStyleConfiguration.CurrentValueLabel.self) } return types diff --git a/Sources/ViewInspector/SwiftUI/TabView.swift b/Sources/ViewInspector/SwiftUI/TabView.swift index 596c0666..6e6098fc 100644 --- a/Sources/ViewInspector/SwiftUI/TabView.swift +++ b/Sources/ViewInspector/SwiftUI/TabView.swift @@ -51,7 +51,7 @@ public extension InspectableView { } func tabItem() throws -> InspectableView { - return try contentForModifierLookup.tabItem(parent: self) + return try contentForModifierLookup.tabItem(parent: self, index: 0) } @available(iOS 14.0, macOS 11.0, tvOS 14.0, *) @@ -75,14 +75,23 @@ public extension InspectableView { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func tabItem(parent: UnwrappedView) throws -> InspectableView { - let rootView = try modifierAttribute( - modifierName: "TabItemTraitKey", path: "modifier|value|some|storage|view|content", - type: Any.self, call: "tabItem") + func tabItem(parent: UnwrappedView, index: Int?) throws -> InspectableView { + let rootView: Any = try { + if let view = try? modifierAttribute( + modifierName: "TabItemTraitKey", path: "modifier|value|some|storage|view|content", + type: Any.self, call: "tabItem") { + return view + } + return try modifierAttribute( + modifierName: "PlatformItemTraitWriter", path: "modifier|source|content|content", + type: Any.self, call: "tabItem") + }() let medium = self.medium.resettingViewModifiers() let view = try InspectableView( try Inspector.unwrap(content: Content(rootView, medium: medium)), parent: parent, call: "tabItem()") - if #available(iOS 14.2, tvOS 14.2, *) { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return view + } else if #available(iOS 14.2, tvOS 14.2, *) { return try InspectableView( try Inspector.unwrap(content: try view.zStack().child(at: 0)), parent: parent, call: "tabItem()") } else { diff --git a/Sources/ViewInspector/SwiftUI/TextAttributes.swift b/Sources/ViewInspector/SwiftUI/TextAttributes.swift index 25d24299..a1b42074 100644 --- a/Sources/ViewInspector/SwiftUI/TextAttributes.swift +++ b/Sources/ViewInspector/SwiftUI/TextAttributes.swift @@ -108,11 +108,13 @@ public extension ViewType.Text.Attributes { func isStrikethrough() throws -> Bool { return try commonTrait(name: "strikethrough") { modifier -> Bool? in guard let child = try? Inspector.attribute(label: "anyTextModifier", value: modifier), - Inspector.typeName(value: child) == "StrikethroughTextModifier", - let active = try? Inspector - .attribute(path: "lineStyle|some|active", value: child, type: Bool.self) + Inspector.typeName(value: child) == "StrikethroughTextModifier" else { return nil } - return active + if let active = try? Inspector + .attribute(path: "lineStyle|some|active", value: child, type: Bool.self) { + return active + } + return (try? Inspector.attribute(path: "lineStyle|some|nsUnderlineStyle", value: child)) != nil } } @@ -127,14 +129,28 @@ public extension ViewType.Text.Attributes { } } + @available(iOS 15.0, tvOS 15.0, macOS 11.6, *) + func strikethroughStyle() throws -> NSUnderlineStyle { + return try commonTrait(name: "strikethrough") { modifier -> NSUnderlineStyle? in + guard let child = try? Inspector.attribute(label: "anyTextModifier", value: modifier), + Inspector.typeName(value: child) == "StrikethroughTextModifier", + let value = try? Inspector + .attribute(path: "lineStyle|some|nsUnderlineStyle", value: child, type: NSUnderlineStyle.self) + else { return nil } + return value + } + } + func isUnderline() throws -> Bool { return try commonTrait(name: "underline") { modifier -> Bool? in guard let child = try? Inspector.attribute(label: "anyTextModifier", value: modifier), - Inspector.typeName(value: child) == "UnderlineTextModifier", - let active = try? Inspector - .attribute(path: "lineStyle|some|active", value: child, type: Bool.self) + Inspector.typeName(value: child) == "UnderlineTextModifier" else { return nil } - return active + if let active = try? Inspector + .attribute(path: "lineStyle|some|active", value: child, type: Bool.self) { + return active + } + return (try? Inspector.attribute(path: "lineStyle|some|nsUnderlineStyle", value: child)) != nil } } @@ -149,6 +165,18 @@ public extension ViewType.Text.Attributes { } } + @available(iOS 15.0, tvOS 15.0, macOS 11.6, *) + func underlineStyle() throws -> NSUnderlineStyle { + return try commonTrait(name: "underline") { modifier -> NSUnderlineStyle? in + guard let child = try? Inspector.attribute(label: "anyTextModifier", value: modifier), + Inspector.typeName(value: child) == "UnderlineTextModifier", + let value = try? Inspector + .attribute(path: "lineStyle|some|nsUnderlineStyle", value: child, type: NSUnderlineStyle.self) + else { return nil } + return value + } + } + func kerning() throws -> CGFloat { return try commonTrait(name: "kerning") { modifier -> CGFloat? in guard let kerning = try? Inspector @@ -268,7 +296,7 @@ public extension Font { } func isFixedSize() -> Bool { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return false } guard let provider = try? Inspector.attribute(path: "provider|base", value: self), Inspector.typeName(value: provider) == "NamedProvider" diff --git a/Sources/ViewInspector/SwiftUI/TextField.swift b/Sources/ViewInspector/SwiftUI/TextField.swift index 88425b28..d6d22af9 100644 --- a/Sources/ViewInspector/SwiftUI/TextField.swift +++ b/Sources/ViewInspector/SwiftUI/TextField.swift @@ -46,19 +46,35 @@ public extension InspectableView where View == ViewType.TextField { func callOnEditingChanged() throws { try guardIsResponsive() typealias Callback = (Bool) -> Void - let callback = try Inspector - .attribute(label: "onEditingChanged", value: content.view, type: Callback.self) + let callback: Callback = try { + if let value = try? Inspector + .attribute(label: "onEditingChanged", value: content.view, type: Callback.self) { + return value + } + return try Inspector + .attribute(path: deprecatedActionsPath("editingChanged"), value: content.view, type: Callback.self) + }() callback(false) } func callOnCommit() throws { try guardIsResponsive() typealias Callback = () -> Void - let callback = try Inspector - .attribute(label: "onCommit", value: content.view, type: Callback.self) + let callback: Callback = try { + if let value = try? Inspector + .attribute(label: "onCommit", value: content.view, type: Callback.self) { + return value + } + return try Inspector + .attribute(path: deprecatedActionsPath("commit"), value: content.view, type: Callback.self) + }() callback() } + private func deprecatedActionsPath(_ action: String) -> String { + return "_state|state|_value|deprecatedActions|some|\(action)" + } + func input() throws -> String { return try inputBinding().wrappedValue } diff --git a/Sources/ViewInspector/SwiftUI/Toggle.swift b/Sources/ViewInspector/SwiftUI/Toggle.swift index a9816d13..c831d59b 100644 --- a/Sources/ViewInspector/SwiftUI/Toggle.swift +++ b/Sources/ViewInspector/SwiftUI/Toggle.swift @@ -32,7 +32,13 @@ public extension InspectableView where View: MultipleViewContent { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) extension ViewType.Toggle: SupplementaryChildrenLabelView { - static var labelViewPath: String { "_label" } + static var labelViewPath: String { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return "label" + } else { + return "_label" + } + } } // MARK: - Custom Attributes @@ -55,8 +61,12 @@ public extension InspectableView where View == ViewType.Toggle { } private func isOnBinding() throws -> Binding { + if let binding = try? Inspector + .attribute(label: "__isOn", value: content.view, type: Binding.self) { + return binding + } return try Inspector - .attribute(label: "__isOn", value: content.view, type: Binding.self) + .attribute(label: "_isOn", value: content.view, type: Binding.self) } } diff --git a/Sources/ViewInspector/SwiftUI/Toolbar.swift b/Sources/ViewInspector/SwiftUI/Toolbar.swift new file mode 100644 index 00000000..3ed43c34 --- /dev/null +++ b/Sources/ViewInspector/SwiftUI/Toolbar.swift @@ -0,0 +1,186 @@ +import SwiftUI + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewType { + + struct Toolbar: KnownViewType { + public static let typePrefix: String = { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + return "ToolbarModifier" + } else { + return "_ToolbarItemGroupModifier" + } + }() + public static func inspectionCall(typeName: String) -> String { + return "toolbar(\(ViewType.indexPlaceholder))" + } + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension ViewType.Toolbar { + struct Item: KnownViewType { + public static var typePrefix: String = "ToolbarItem" + } + struct ItemGroup: KnownViewType { + public static var typePrefix: String = "ToolbarItemGroup" + } +} + +// MARK: - Extraction + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension InspectableView { + + func toolbar(_ index: Int? = nil) throws -> InspectableView { + return try contentForModifierLookup.toolbar(parent: self, index: index) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension Content { + + func toolbar(parent: UnwrappedView, index: Int?) throws -> InspectableView { + let modifierName = ViewType.Toolbar.typePrefix + let modifier = try self.modifier({ modifier -> Bool in + return modifier.modifierType.contains(modifierName) + }, call: "toolbar", index: index ?? 0) + let root = try Inspector.attribute(label: "modifier", value: modifier) + let medium = self.medium.resettingViewModifiers() + let content = try Inspector.unwrap(content: Content(root, medium: medium)) + let call = ViewType.inspectionCall( + base: ViewType.Toolbar.inspectionCall(typeName: ""), index: index) + return try .init(content, parent: parent, call: call, index: index) + } +} + +// MARK: - Content + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Toolbar.Item: SingleViewContent { + + public static func child(_ content: Content) throws -> Content { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.unwrap(view: view, medium: content.medium) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Toolbar.Item: MultipleViewContent { + + public static func children(_ content: Content) throws -> LazyGroup { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.viewsInContainer(view: view, medium: content.medium) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Toolbar.ItemGroup: SingleViewContent { + + public static func child(_ content: Content) throws -> Content { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.unwrap(view: view, medium: content.medium) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Toolbar.ItemGroup: MultipleViewContent { + + public static func children(_ content: Content) throws -> LazyGroup { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.viewsInContainer(view: view, medium: content.medium) + } +} + +// MARK: - Custom Attributes + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +public extension InspectableView where View == ViewType.Toolbar { + + func identifier() throws -> String? { + return try Inspector.attribute( + label: "id", value: content.view, type: String?.self) + } + + func item(_ index: Int = 0) throws -> InspectableView { + let element = try self.element(index) + return try .init(Content(element, medium: content.medium), parent: self, index: index) + } + + func itemGroup(_ index: Int = 0) throws -> InspectableView { + let element = try self.element(index) + return try .init(Content(element, medium: content.medium), parent: self, index: index) + } + + private func element(_ index: Int) throws -> Any { + do { + return try Inspector.attribute(path: "content|value|.\(index)", value: content.view) + } catch { + if index == 0, let value = try? Inspector + .attribute(path: "content|value", value: content.view) { + return value + } + throw InspectionError.viewNotFound(parent: "toolbar item at index \(index)") + } + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension Content { + func toolbarElementsCount() -> Int { + var index: Int = -1 + var couldLocateItem = false + repeat { + index += 1 + couldLocateItem = (try? Inspector.attribute(path: "content|value|.\(index)", value: view)) != nil + } while couldLocateItem + if index == 0, (try? Inspector.attribute(path: "content|value", value: view)) != nil { + return 1 + } + return index + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.Toolbar: SupplementaryChildren { + static func supplementaryChildren(_ parent: UnwrappedView) throws -> LazyGroup { + guard let toolbar = parent as? InspectableView + else { return .empty } + return .init(count: parent.content.toolbarElementsCount()) { index in + if let itemGroup = try? toolbar.itemGroup(index) { + return itemGroup + } + return try toolbar.item(index) + } + } +} + +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) +public extension InspectableView where View == ViewType.Toolbar.Item { + + func identifier() throws -> String { + return try Inspector.attribute( + label: "identifier", value: content.view, type: String.self) + } + + func placement() throws -> ToolbarItemPlacement { + return try Inspector.attribute( + label: "placement", value: content.view, type: ToolbarItemPlacement.self) + } + + func showsByDefault() throws -> Bool { + return try Inspector.attribute( + label: "showsByDefault", value: content.view, type: Bool.self) + } +} + +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) +public extension InspectableView where View == ViewType.Toolbar.ItemGroup { + func placement() throws -> ToolbarItemPlacement { + return try Inspector.attribute( + label: "placement", value: content.view, type: ToolbarItemPlacement.self) + } +} + +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) +extension ToolbarItemPlacement: BinaryEquatable { } diff --git a/Sources/ViewInspector/SwiftUI/TouchBar.swift b/Sources/ViewInspector/SwiftUI/TouchBar.swift index 7e141286..44c0d4ba 100644 --- a/Sources/ViewInspector/SwiftUI/TouchBar.swift +++ b/Sources/ViewInspector/SwiftUI/TouchBar.swift @@ -45,7 +45,7 @@ public extension InspectableView where View == ViewType.TouchBar { public extension InspectableView { func touchBar() throws -> InspectableView { - return try contentForModifierLookup.touchBar(parent: self) + return try contentForModifierLookup.touchBar(parent: self, index: 0) } func touchBarItemPrincipal() throws -> Bool { @@ -70,19 +70,21 @@ public extension InspectableView { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func touchBar(parent: UnwrappedView) throws -> InspectableView { + func touchBar(parent: UnwrappedView, index: Int?) throws -> InspectableView { let rootView = try modifierAttribute( modifierName: "_TouchBarModifier", path: "modifier|touchBar", - type: Any.self, call: "touchBar") + type: Any.self, call: "touchBar", index: index ?? 0) let content = try Inspector.unwrap(content: Content(rootView)) - return try .init(content, parent: parent, call: "touchBar()") + let call = ViewType.inspectionCall( + base: ViewType.TouchBar.inspectionCall(typeName: ""), index: nil) + return try .init(content, parent: parent, call: call) } } #else @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) internal extension Content { - func touchBar(parent: UnwrappedView) throws -> InspectableView { + func touchBar(parent: UnwrappedView, index: Int?) throws -> InspectableView { throw InspectionError.notSupported("Not supported on this platform") } } diff --git a/Sources/ViewInspector/SwiftUI/UnaryViewAdaptor.swift b/Sources/ViewInspector/SwiftUI/UnaryViewAdaptor.swift new file mode 100644 index 00000000..6d4c6688 --- /dev/null +++ b/Sources/ViewInspector/SwiftUI/UnaryViewAdaptor.swift @@ -0,0 +1,17 @@ +import SwiftUI + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +internal extension ViewType { + struct UnaryViewAdaptor { } +} + +// MARK: - Content Extraction + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +extension ViewType.UnaryViewAdaptor: SingleViewContent { + + static func child(_ content: Content) throws -> Content { + let view = try Inspector.attribute(label: "content", value: content.view) + return try Inspector.unwrap(view: view, medium: content.medium) + } +} diff --git a/Sources/ViewInspector/ViewHosting.swift b/Sources/ViewInspector/ViewHosting.swift index a22afdaa..7e49ec98 100644 --- a/Sources/ViewInspector/ViewHosting.swift +++ b/Sources/ViewInspector/ViewHosting.swift @@ -1,15 +1,18 @@ import SwiftUI +import Combine #if canImport(UIKit) import UIKit #endif +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct ViewHosting { } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension ViewHosting { struct ViewId: Hashable { let function: String + var key: String { function } } static func host(view: V, size: CGSize? = nil, function: String = #function) where V: View { @@ -22,6 +25,18 @@ public extension ViewHosting { } return unwrapped.medium }() + #if os(watchOS) + do { + store(Hosted(medium: medium), viewId: viewId) + try watchOS(host: AnyView(view), viewId: viewId) + } catch { + fatalError(error.localizedDescription) + /* + If you're running ViewInspector's tests on watchOS, launch them + from another Xcode project at ".watchOS/watchOS.xcodeproj" + */ + } + #else let parentVC = rootViewController let childVC = hostVC(view) let size = size ?? parentVC.view.bounds.size @@ -39,18 +54,58 @@ public extension ViewHosting { ]) didMove(childVC, to: parentVC) window.layoutIfNeeded() + #endif } static func expel(function: String = #function) { let viewId = ViewId(function: function) + #if os(watchOS) + _ = expel(viewId: viewId) + try? watchOS(host: nil, viewId: viewId) + #else guard let hosted = expel(viewId: viewId) else { return } let childVC = hosted.viewController willMove(childVC, to: nil) childVC.view.removeFromSuperview() childVC.removeFromParent() didMove(childVC, to: nil) + #endif } + #if os(watchOS) + private static func watchOS(host view: AnyView?, viewId: ViewId) throws { + typealias Subject = CurrentValueSubject<[(String, AnyView)], Never> + let ext = WKExtension.shared() + guard let subject: Subject = { + if let delegate = ext.delegate, + let subject = try? Inspector + .attribute(path: "fallbackDelegate|some|extension|testViewSubject", + value: delegate, type: Subject.self) { + return subject + } + if let rootIC = ext.rootInterfaceController, + let subject = try? Inspector + .attribute(label: "testViewSubject", value: rootIC, type: Subject.self) { + return subject + } + return nil + }() else { + throw InspectionError.notSupported( + """ + View hosting for watchOS is not set up. Please follow this guide: \ + https://github.com/nalexn/ViewInspector/blob/master/guide_watchOS.md + """) + } + var array = subject.value + if let view = view { + array.append((viewId.key, view)) + } else if let index = array.firstIndex(where: { $0.0 == viewId.key }) { + array.remove(at: index) + } + subject.send(array) + } + #endif + internal static func medium(function: String = #function) -> Content.Medium { let viewId = ViewHosting.ViewId(function: function) return hosted[viewId]?.medium ?? .empty @@ -65,7 +120,7 @@ private extension ViewHosting { struct Hosted { #if os(macOS) let viewController: NSViewController - #else + #elseif os(iOS) || os(tvOS) let viewController: UIViewController #endif let medium: Content.Medium @@ -73,7 +128,7 @@ private extension ViewHosting { private static var hosted: [ViewId: Hosted] = [:] #if os(macOS) static var window: NSWindow = makeWindow() - #else + #elseif os(iOS) || os(tvOS) static var window: UIWindow = makeWindow() #endif @@ -91,7 +146,7 @@ private extension ViewHosting { window.layoutIfNeeded() return window } - #else + #elseif os(iOS) || os(tvOS) static func makeWindow() -> UIWindow { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() @@ -111,7 +166,7 @@ private extension ViewHosting { static func hostVC(_ view: V) -> NSHostingController where V: View { NSHostingController(rootView: view) } - #else + #elseif os(iOS) || os(tvOS) static var rootViewController: UIViewController { window.rootViewController! } @@ -127,7 +182,7 @@ private extension ViewHosting { } static func didMove(_ child: NSViewController, to parent: NSViewController?) { } - #else + #elseif os(iOS) || os(tvOS) static func willMove(_ child: UIViewController, to parent: UIViewController?) { child.willMove(toParent: parent) } @@ -147,6 +202,7 @@ private extension ViewHosting { } } +#if !os(watchOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private extension NSLayoutConstraint { #if os(macOS) @@ -161,6 +217,7 @@ private extension NSLayoutConstraint { } #endif } +#endif // MARK: - RootViewController for macOS @@ -184,7 +241,7 @@ private class RootViewController: NSViewController { // MARK: - UIView lookup -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) internal extension ViewHosting { #if os(macOS) static func lookup(_ view: V.Type) throws -> V.NSViewType @@ -210,7 +267,7 @@ internal extension ViewHosting { else { throw InspectionError.viewNotFound(parent: name) } return vc } - #else + #elseif os(iOS) || os(tvOS) static func lookup(_ view: V.Type) throws -> V.UIViewType where V: Inspectable & UIViewRepresentable { let name = Inspector.typeName(type: view) @@ -229,9 +286,40 @@ internal extension ViewHosting { .first else { throw InspectionError.viewNotFound(parent: name) } return vc } + #elseif os(watchOS) + static func lookup(_ view: V.Type) throws -> V.WKInterfaceObjectType + where V: Inspectable & WKInterfaceObjectRepresentable { + let name = Inspector.typeName(type: view) + guard let rootVC = WKExtension.shared().rootInterfaceController, + let viewCache = try? Inspector.attribute(path: """ + super|$__lazy_storage_$_hostingController|some|\ + host|renderer|renderer|some|viewCache|map + """, value: rootVC, type: ArrayConvertible.self).allValues(), + let object = viewCache.compactMap({ value in + try? Inspector.attribute( + path: "view|representedViewProvider", + value: value, type: V.WKInterfaceObjectType.self) + }).first + else { + throw InspectionError.viewNotFound(parent: name) + } + return object + } #endif } +#if os(watchOS) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +internal protocol ArrayConvertible { + func allValues() -> [Any] +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +extension Dictionary: ArrayConvertible { + func allValues() -> [Any] { Array(values) as [Any] } +} +#endif + #if os(macOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private extension NSView { @@ -263,7 +351,7 @@ private extension NSViewController { return presented + children } } -#else +#elseif os(iOS) || os(tvOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private extension UIView { func descendant(nameTraits: [String]) -> UIView? { diff --git a/Sources/ViewInspector/ViewSearch.swift b/Sources/ViewInspector/ViewSearch.swift index 3f4bf0e3..0f1b88b4 100644 --- a/Sources/ViewInspector/ViewSearch.swift +++ b/Sources/ViewInspector/ViewSearch.swift @@ -347,7 +347,7 @@ private extension UnwrappedView { if name.hasPrefix("EnvironmentReaderView") { return "navigationBarItems" } - if name.hasPrefix("PopoverPresentationModifier") { + if name.hasPrefix(ViewType.Popover.standardModifierName) { return "popover" } if let inspectable = view as? Inspectable { diff --git a/Sources/ViewInspector/ViewSearchIndex.swift b/Sources/ViewInspector/ViewSearchIndex.swift index 2fc3a530..c9a4d724 100644 --- a/Sources/ViewInspector/ViewSearchIndex.swift +++ b/Sources/ViewInspector/ViewSearchIndex.swift @@ -7,10 +7,12 @@ internal extension ViewSearch { private static var index: [String: [ViewIdentity]] = { let identities: [ViewIdentity] = [ - .init(ViewType.ActionSheet.self), .init(ViewType.Alert.self), .init(ViewType.AlertButton.self), + .init(ViewType.ActionSheet.self), + .init(ViewType.Alert.self), .init(ViewType.AlertButton.self), .init(ViewType.AngularGradient.self), .init(ViewType.AnyView.self), .init(ViewType.Button.self), .init(ViewType.Color.self), .init(ViewType.ColorPicker.self), + .init(ViewType.ConfirmationDialog.self), .init(ViewType.DatePicker.self), .init(ViewType.DisclosureGroup.self), .init(ViewType.Divider.self), .init(ViewType.EditButton.self), .init(ViewType.EmptyView.self), @@ -29,27 +31,37 @@ internal extension ViewSearch { .init(ViewType.NavigationLink.self), .init(ViewType.NavigationView.self), .init(ViewType.OutlineGroup.self), .init(ViewType.PasteButton.self), .init(ViewType.Picker.self), - .init(ViewType.Popover.self), .init(ViewType.ProgressView.self), + .init(ViewType.Popover.self, genericTypeName: nil), + .init(ViewType.ProgressView.self), .init(ViewType.RadialGradient.self), + .init(ViewType.SafeAreaInset.self, genericTypeName: nil), .init(ViewType.ScrollView.self), .init(ViewType.ScrollViewReader.self), .init(ViewType.Section.self), .init(ViewType.SecureField.self), - .init(ViewType.Sheet.self, genericTypeName: nil), + .init(ViewType.Sheet.self, genericTypeName: "Sheet"), .init(ViewType.Slider.self), .init(ViewType.Spacer.self), .init(ViewType.Stepper.self), .init(ViewType.StyleConfiguration.Label.self), .init(ViewType.StyleConfiguration.Content.self), .init(ViewType.StyleConfiguration.Title.self), .init(ViewType.StyleConfiguration.Icon.self), .init(ViewType.StyleConfiguration.CurrentValueLabel.self), .init(ViewType.TabView.self), .init(ViewType.Text.self), .init(ViewType.TextEditor.self), .init(ViewType.TextField.self), - .init(ViewType.Toggle.self), .init(ViewType.TouchBar.self), .init(ViewType.TupleView.self), + .init(ViewType.Toggle.self), .init(ViewType.TouchBar.self), + .init(ViewType.TupleView.self), .init(ViewType.Toolbar.self), + .init(ViewType.Toolbar.Item.self, genericTypeName: nil), + .init(ViewType.Toolbar.ItemGroup.self, genericTypeName: nil), .init(ViewType.ViewModifierContent.self), .init(ViewType.VSplitView.self), .init(ViewType.VStack.self), .init(ViewType.ZStack.self) ] var index = [String: [ViewIdentity]](minimumCapacity: 26) // alphabet identities.forEach { identity in - let letter = String(identity.viewType.typePrefix.prefix(1)) - var array = index[letter] ?? [] - array.append(identity) - index[letter] = array + let names = identity.viewType.namespacedPrefixes + .compactMap { $0.components(separatedBy: ".").last } + + [identity.viewType.typePrefix] + let letters = Set(names).map { String($0.prefix(1)) } + letters.forEach { letter in + var array = index[letter] ?? [] + array.append(identity) + index[letter] = array + } } return index }() @@ -60,6 +72,9 @@ internal extension ViewSearch { let letter = String(viewType.typePrefix.prefix(1)) return index[letter]?.first(where: { $0.viewType == viewType }) } + if content.isShape { + return .init(ViewType.Shape.self) + } let shortPrefix = Inspector.typeName(value: content.view, prefixOnly: true) let longPrefix = Inspector.typeName(value: content.view, namespaced: true, prefixOnly: true) if shortPrefix.count > 0, @@ -149,7 +164,8 @@ internal extension ViewSearch { let descendants = try supplementary(parent) return .init(count: descendants.count) { index -> UnwrappedView in var view = try descendants.element(at: index) - if !(view is InspectableView) { + if Inspector.isTupleView(view.content.view) || + !(view is InspectableView) { view.isUnwrappedSupplementaryChild = true return view } @@ -235,25 +251,37 @@ internal extension ViewSearch { static private(set) var modifierIdentities: [ModifierIdentity] = [ .init(name: "_OverlayModifier", builder: { parent, index in - try parent.content.overlay(parent: parent) + try parent.content.overlay(parent: parent, index: index) }), .init(name: "_BackgroundModifier", builder: { parent, index in - try parent.content.background(parent: parent) + try parent.content.background(parent: parent, index: index) + }), + .init(name: ViewType.Toolbar.typePrefix, builder: { parent, index in + try parent.content.toolbar(parent: parent, index: index) + }), + .init(name: ViewType.ConfirmationDialog.typePrefix, builder: { parent, index in + try parent.content.confirmationDialog(parent: parent, index: index) }), - .init(name: "PopoverPresentationModifier", builder: { parent, index in - try parent.content.popover(parent: parent) + .init(name: ViewType.SafeAreaInset.typePrefix, builder: { parent, index in + try parent.content.safeAreaInset(parent: parent, index: index) + }), + .init(name: ViewType.Popover.standardModifierName, builder: { parent, index in + try parent.content.popover(parent: parent, index: index) }), .init(name: "_MaskEffect", builder: { parent, index in - try parent.content.mask(parent: parent) + try parent.content.mask(parent: parent, index: index) }), .init(name: "_TraitWritingModifier", builder: { parent, index in - try parent.content.tabItem(parent: parent) + try parent.content.tabItem(parent: parent, index: index) + }), + .init(name: "PlatformItemTraitWriter", builder: { parent, index in - try parent.content.listRowBackground(parent: parent) + try parent.content.listRowBackground(parent: parent, index: index) }), .init(name: "_TouchBarModifier", builder: { parent, index in - try parent.content.touchBar(parent: parent) + try parent.content.touchBar(parent: parent, index: index) }), ] @@ -289,8 +317,10 @@ internal extension Content { }) let sheets = sheetModifierDescendants(parent: parent) let customModifiers = customViewModifiers() - return .init(count: identities.count, { index -> UnwrappedView in - try identities[index].builder(parent, nil) + let modifiersCount = modifierNames.count + return .init(count: identities.count * modifiersCount, { index -> UnwrappedView in + try identities[index / modifiersCount] + .builder(parent, (index % modifiersCount).nilIfZero) }) + sheets + .init(count: customModifiers.count, { index -> UnwrappedView in let modifier = customModifiers[index] let name = Inspector.typeName(value: modifier) @@ -310,18 +340,32 @@ internal extension Content { private func sheetModifierDescendants(parent: UnwrappedView) -> LazyGroup { let sheetModifiers = sheetsForSearch() + let alertModifiers = alertsForSearch() #if os(macOS) let actionSheetModifiers: [ViewSearch.ModifierIdentity] = [] #else let actionSheetModifiers = actionSheetsForSearch() #endif - let alertModifiers = alertsForSearch() - return .init(count: sheetModifiers.count, { index -> UnwrappedView in - try sheetModifiers[index].builder(parent, index) - }) + .init(count: actionSheetModifiers.count, { index -> UnwrappedView in - try actionSheetModifiers[index].builder(parent, index) - }) + .init(count: alertModifiers.count, { index -> UnwrappedView in - try alertModifiers[index].builder(parent, index) - }) + #if os(iOS) || os(macOS) + let popoverModifiers = popoversForSearch() + #else + let popoverModifiers: [ViewSearch.ModifierIdentity] = [] + #endif + return + .init(count: sheetModifiers.count, { index -> UnwrappedView in + try sheetModifiers[index].builder(parent, index) + }) + .init(count: actionSheetModifiers.count, { index -> UnwrappedView in + try actionSheetModifiers[index].builder(parent, index) + }) + .init(count: alertModifiers.count, { index -> UnwrappedView in + try alertModifiers[index].builder(parent, index) + }) + .init(count: popoverModifiers.count, { index -> UnwrappedView in + try popoverModifiers[index].builder(parent, index) + }) + } +} + +private extension Int { + var nilIfZero: Int? { + return self == 0 ? nil : self } } diff --git a/Tests/ViewInspectorTests/BaseTypesTests.swift b/Tests/ViewInspectorTests/BaseTypesTests.swift index acda8918..a042ebae 100644 --- a/Tests/ViewInspectorTests/BaseTypesTests.swift +++ b/Tests/ViewInspectorTests/BaseTypesTests.swift @@ -51,7 +51,7 @@ func XCTAssertThrows(_ expression: @autoclosure () throws -> T, _ message: St file: StaticString = #file, line: UInt = #line) { do { _ = try expression() - XCTFail("Expression did not throw any error") + XCTFail("Expression did not throw any error", file: file, line: line) } catch let error { XCTAssertEqual(error.localizedDescription, message, file: file, line: line) } diff --git a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureChangedTests.swift b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureChangedTests.swift index 26a19721..f51119a6 100644 --- a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureChangedTests.swift +++ b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureChangedTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class CommonComposedGestureChangedTests { let testCase: XCTestCase diff --git a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureEndedTests.swift b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureEndedTests.swift index 9848c0a8..b336743c 100644 --- a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureEndedTests.swift +++ b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureEndedTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class CommonComposedGestureEndedTests { let testCase: XCTestCase diff --git a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureTests.swift b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureTests.swift index e932b4b9..4857b2d1 100644 --- a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class CommonComposedGestureTests { let type: U.Type diff --git a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureUpdatingTests.swift b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureUpdatingTests.swift index 1e0fdc96..de13e427 100644 --- a/Tests/ViewInspectorTests/Gestures/CommonComposedGestureUpdatingTests.swift +++ b/Tests/ViewInspectorTests/Gestures/CommonComposedGestureUpdatingTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class CommonComposedGestureUpdatingTests { @GestureState var gestureState = CGSize.zero diff --git a/Tests/ViewInspectorTests/Gestures/ComposedGestureExampleTests.swift b/Tests/ViewInspectorTests/Gestures/ComposedGestureExampleTests.swift index c981a562..7de18d9d 100644 --- a/Tests/ViewInspectorTests/Gestures/ComposedGestureExampleTests.swift +++ b/Tests/ViewInspectorTests/Gestures/ComposedGestureExampleTests.swift @@ -5,6 +5,7 @@ import Combine @available(iOS 13.0, macOS 11, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class ComposedGestureExampleTests: XCTestCase { func testComposedGestureFirst() throws { @@ -105,6 +106,7 @@ final class ComposedGestureExampleTests: XCTestCase { @available(iOS 13.0, macOS 11, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) struct TestGestureView10: View & Inspectable { @State var scale: CGFloat = 1.0 @State var angle = Angle(degrees: 0) @@ -137,6 +139,7 @@ struct TestGestureView10: View & Inspectable { @available(iOS 13.0, macOS 11, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) struct TestGestureView11: View & Inspectable { @State var scale: CGFloat = 1.0 @State var angle = Angle(degrees: 0) @@ -168,6 +171,7 @@ struct TestGestureView11: View & Inspectable { @available(iOS 13.0, macOS 11, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) struct TestGestureView12: View & Inspectable { internal let inspection = Inspection() diff --git a/Tests/ViewInspectorTests/Gestures/ExclusiveGestureChildrenTests.swift b/Tests/ViewInspectorTests/Gestures/ExclusiveGestureChildrenTests.swift index f6e1a6ef..a5b0f363 100644 --- a/Tests/ViewInspectorTests/Gestures/ExclusiveGestureChildrenTests.swift +++ b/Tests/ViewInspectorTests/Gestures/ExclusiveGestureChildrenTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class ExclusiveGestureChildrenTests: XCTestCase { typealias GUT = ExclusiveGesture diff --git a/Tests/ViewInspectorTests/Gestures/ExclusiveGestureTests.swift b/Tests/ViewInspectorTests/Gestures/ExclusiveGestureTests.swift index 43add667..3c40b6a3 100644 --- a/Tests/ViewInspectorTests/Gestures/ExclusiveGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/ExclusiveGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class ExclusiveGestureTests: XCTestCase { @GestureState var gestureState = CGSize.zero diff --git a/Tests/ViewInspectorTests/Gestures/GestureModifiers/GestureModifierTests.swift b/Tests/ViewInspectorTests/Gestures/GestureModifiers/GestureModifierTests.swift index d51f172a..1427866b 100644 --- a/Tests/ViewInspectorTests/Gestures/GestureModifiers/GestureModifierTests.swift +++ b/Tests/ViewInspectorTests/Gestures/GestureModifiers/GestureModifierTests.swift @@ -33,8 +33,8 @@ final class GestureModifierTests: XCTestCase { "EmptyView does not have 'gesture(DragGesture.self)' modifier") } - @available(tvOS 14.0, *) func testGestureInspectionFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .gesture(LongPressGesture()) XCTAssertThrows( @@ -42,8 +42,8 @@ final class GestureModifierTests: XCTestCase { "Type mismatch: LongPressGesture is not DragGesture") } - @available(tvOS 14.0, *) func testGestureInspectionWithIndex1() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .gesture(DragGesture()) .gesture(LongPressGesture()) @@ -51,8 +51,8 @@ final class GestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().gesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testGestureInspectionWithIndex2() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .gesture(DragGesture()) .highPriorityGesture(TapGesture()) @@ -61,8 +61,8 @@ final class GestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().gesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testGestureInspectionWithIndexFailureDueToNoModifier() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .gesture(DragGesture()) .gesture(LongPressGesture()) @@ -71,8 +71,8 @@ final class GestureModifierTests: XCTestCase { "EmptyView does not have 'gesture(DragGesture.self)' modifier at index 2") } - @available(tvOS 14.0, *) func testGestureInspectionWithIndexFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .gesture(DragGesture()) .gesture(LongPressGesture()) @@ -90,8 +90,8 @@ final class GestureModifierTests: XCTestCase { XCTAssertEqual(path, "emptyView().gesture(DragGesture.self)") } - @available(tvOS 14.0, *) func testGestureInspectionWithIndexPathToRoot() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .padding(100) .gesture(DragGesture()) diff --git a/Tests/ViewInspectorTests/Gestures/GestureModifiers/HighPriorityGestureModifierTests.swift b/Tests/ViewInspectorTests/Gestures/GestureModifiers/HighPriorityGestureModifierTests.swift index 0f1385d7..267eae9b 100644 --- a/Tests/ViewInspectorTests/Gestures/GestureModifiers/HighPriorityGestureModifierTests.swift +++ b/Tests/ViewInspectorTests/Gestures/GestureModifiers/HighPriorityGestureModifierTests.swift @@ -36,8 +36,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { "EmptyView does not have 'highPriorityGesture(DragGesture.self)' modifier") } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .highPriorityGesture(LongPressGesture()) XCTAssertThrows( @@ -45,8 +45,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { "Type mismatch: LongPressGesture is not DragGesture") } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionWithIndex1() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .highPriorityGesture(DragGesture()) .highPriorityGesture(LongPressGesture()) @@ -54,8 +54,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().highPriorityGesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionWithIndex2() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .highPriorityGesture(DragGesture()) .gesture(TapGesture()) @@ -64,8 +64,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().highPriorityGesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionWithIndexFailureDueToNoModifier() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .highPriorityGesture(DragGesture()) .highPriorityGesture(LongPressGesture()) @@ -74,8 +74,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { "EmptyView does not have 'highPriorityGesture(DragGesture.self)' modifier at index 2") } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionWithIndexFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .highPriorityGesture(DragGesture()) .highPriorityGesture(LongPressGesture()) @@ -92,8 +92,8 @@ final class HighPriorityGestureModifierTests: XCTestCase { XCTAssertEqual(path, "emptyView().highPriorityGesture(DragGesture.self)") } - @available(tvOS 14.0, *) func testHighPriorityGestureInspectionWithIndexPathToRoot() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .padding(100) .highPriorityGesture(DragGesture()) diff --git a/Tests/ViewInspectorTests/Gestures/GestureModifiers/SimultaneousGestureModifierTests.swift b/Tests/ViewInspectorTests/Gestures/GestureModifiers/SimultaneousGestureModifierTests.swift index 9dc0eec3..c22834aa 100644 --- a/Tests/ViewInspectorTests/Gestures/GestureModifiers/SimultaneousGestureModifierTests.swift +++ b/Tests/ViewInspectorTests/Gestures/GestureModifiers/SimultaneousGestureModifierTests.swift @@ -36,8 +36,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { "EmptyView does not have 'simultaneousGesture(DragGesture.self)' modifier") } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .simultaneousGesture(LongPressGesture()) XCTAssertThrows( @@ -45,8 +45,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { "Type mismatch: LongPressGesture is not DragGesture") } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionWithIndex1() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .simultaneousGesture(DragGesture()) .simultaneousGesture(LongPressGesture()) @@ -54,8 +54,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().simultaneousGesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionWithIndex2() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .simultaneousGesture(DragGesture()) .gesture(TapGesture()) @@ -64,8 +64,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().simultaneousGesture(LongPressGesture.self, 1)) } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionWithIndexFailureDueToNoModifier() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .simultaneousGesture(DragGesture()) .simultaneousGesture(LongPressGesture()) @@ -74,8 +74,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { "EmptyView does not have 'simultaneousGesture(DragGesture.self)' modifier at index 2") } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionWithIndexFailureDueToTypeMismatch() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .simultaneousGesture(DragGesture()) .simultaneousGesture(LongPressGesture()) @@ -92,8 +92,8 @@ final class SimultaneousGestureModifierTests: XCTestCase { XCTAssertEqual(path, "emptyView().simultaneousGesture(DragGesture.self)") } - @available(tvOS 14.0, *) func testSimultaneousGestureInspectionWithIndexPathToRoot() throws { + guard #available(tvOS 14.0, *) else { return } let sut = EmptyView() .padding(100) .simultaneousGesture(DragGesture()) diff --git a/Tests/ViewInspectorTests/Gestures/LongPressGestureTests.swift b/Tests/ViewInspectorTests/Gestures/LongPressGestureTests.swift index 1791a1d0..93bbccf2 100644 --- a/Tests/ViewInspectorTests/Gestures/LongPressGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/LongPressGestureTests.swift @@ -5,38 +5,50 @@ import Combine // MARK: - Long Press Gesture Tests -@available(iOS 13.0, macOS 10.15, tvOS 14.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) final class LongPressGestureTests: XCTestCase { var longPressFinished: Bool? - var longPressValue: LongPressGesture.Value? - var gestureTests: CommonGestureTests? + private var _longPressValue: Any? + @available(tvOS 14.0, *) + private func longPressValue() throws -> LongPressGesture.Value { + return try Inspector.cast(value: _longPressValue!, type: LongPressGesture.Value.self) + } + + private var _gestureTests: Any? + @available(tvOS 14.0, *) + private func gestureTests() throws -> CommonGestureTests { + return try Inspector.cast(value: _gestureTests!, type: CommonGestureTests.self) + } override func setUpWithError() throws { + guard #available(tvOS 14.0, *) else { return } longPressFinished = false - longPressValue = LongPressGesture.Value(finished: longPressFinished!) + _longPressValue = LongPressGesture.Value(finished: longPressFinished!) - gestureTests = CommonGestureTests(testCase: self, + _gestureTests = CommonGestureTests(testCase: self, gesture: LongPressGesture(), - value: longPressValue!, + value: try longPressValue(), assert: assertLongPressValue) } override func tearDownWithError() throws { longPressFinished = nil - longPressValue = nil - gestureTests = nil + _longPressValue = nil + _gestureTests = nil } func testCreateLongPressGestureValue() throws { + guard #available(tvOS 14.0, *) else { return } XCTAssertNotNil(longPressFinished) - let value = try XCTUnwrap(longPressValue) + let value = try longPressValue() assertLongPressValue(value) } func testLongPressGestureMask() throws { - try gestureTests!.maskTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().maskTest() } #if !os(tvOS) @@ -50,93 +62,110 @@ final class LongPressGestureTests: XCTestCase { #endif func testLongPressGestureWithUpdatingModifier() throws { - try gestureTests!.propertiesWithUpdatingModifierTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().propertiesWithUpdatingModifierTest() } func testLongPressGestureWithOnChangedModifier() throws { - try gestureTests!.propertiesWithOnChangedModifierTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().propertiesWithOnChangedModifierTest() } func testLongPressGestureWithOnEndedModifier() throws { - try gestureTests!.propertiesWithOnEndedModifierTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().propertiesWithOnEndedModifierTest() } #if os(macOS) func testLongPressGestureWithModifiers() throws { - try gestureTests!.propertiesWithModifiersTest() + try gestureTests().propertiesWithModifiersTest() } #endif func testLongPressGestureFailure() throws { - try gestureTests!.propertiesFailureTest("LongPressGesture") + guard #available(tvOS 14.0, *) else { return } + try gestureTests().propertiesFailureTest("LongPressGesture") } func testLongPressGestureCallUpdating() throws { - try gestureTests!.callUpdatingTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callUpdatingTest() } func testLongPressGestureCallUpdatingNotFirst() throws { - try gestureTests!.callUpdatingNotFirstTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callUpdatingNotFirstTest() } func testLongPressGestureCallUpdatingMultiple() throws { - try gestureTests!.callUpdatingMultipleTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callUpdatingMultipleTest() } func testLongPressGestureCallUpdatingFailure() throws { - try gestureTests!.callUpdatingFailureTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callUpdatingFailureTest() } func testLongPressGestureCallOnChanged() throws { - try gestureTests!.callOnChangedTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnChangedTest() } func testLongPressGestureCallOnChangedNotFirst() throws { - try gestureTests!.callOnChangedNotFirstTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnChangedNotFirstTest() } func testLongPressGestureCallOnChangedMultiple() throws { - try gestureTests!.callOnChangedMultipleTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnChangedMultipleTest() } func testLongPressGestureCallOnChangedFailure() throws { - try gestureTests!.callOnChangedFailureTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnChangedFailureTest() } func testLongPressGestureCallOnEnded() throws { - try gestureTests!.callOnEndedTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnEndedTest() } func testLongPressGestureCallOnEndedNotFirst() throws { - try gestureTests!.callOnEndedNotFirstTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnEndedNotFirstTest() } func testLongPressGestureCallOnEndedMultiple() throws { - try gestureTests!.callOnEndedMultipleTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnEndedMultipleTest() } func testLongPressGestureCallOnEndedFailure() throws { - try gestureTests!.callOnEndedFailureTest() + guard #available(tvOS 14.0, *) else { return } + try gestureTests().callOnEndedFailureTest() } #if os(macOS) func testLongPressGestureModifiers() throws { - try gestureTests!.modifiersTest() + try gestureTests().modifiersTest() } func testLongPressGestureModifiersNotFirst() throws { - try gestureTests!.modifiersNotFirstTest() + try gestureTests().modifiersNotFirstTest() } func testLongPressGestureModifiersMultiple() throws { - try gestureTests!.modifiersMultipleTest() + try gestureTests().modifiersMultipleTest() } func testLongPressGestureModifiersNone() throws { - try gestureTests!.modifiersNoneTest() + try gestureTests().modifiersNoneTest() } #endif + @available(tvOS 14.0, *) func assertLongPressValue( _ value: LongPressGesture.Value, file: StaticString = #filePath, diff --git a/Tests/ViewInspectorTests/Gestures/MagnificationGestureTests.swift b/Tests/ViewInspectorTests/Gestures/MagnificationGestureTests.swift index 7df13af7..57096c3e 100644 --- a/Tests/ViewInspectorTests/Gestures/MagnificationGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/MagnificationGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class MagnificationGestureTests: XCTestCase { var magnificationMagnifyBy: CGFloat? diff --git a/Tests/ViewInspectorTests/Gestures/RotationGestureTests.swift b/Tests/ViewInspectorTests/Gestures/RotationGestureTests.swift index 0f7e7b1d..1bf26497 100644 --- a/Tests/ViewInspectorTests/Gestures/RotationGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/RotationGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class RotationGestureTests: XCTestCase { var rotationAngle: Angle? diff --git a/Tests/ViewInspectorTests/Gestures/SequenceGestureChildrenTests.swift b/Tests/ViewInspectorTests/Gestures/SequenceGestureChildrenTests.swift index 51b8b5d3..f4d7d8f3 100644 --- a/Tests/ViewInspectorTests/Gestures/SequenceGestureChildrenTests.swift +++ b/Tests/ViewInspectorTests/Gestures/SequenceGestureChildrenTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class SequenceGestureChildrenTests: XCTestCase { typealias GUT = SequenceGesture diff --git a/Tests/ViewInspectorTests/Gestures/SequenceGestureTests.swift b/Tests/ViewInspectorTests/Gestures/SequenceGestureTests.swift index 5a81078c..e5b8d803 100644 --- a/Tests/ViewInspectorTests/Gestures/SequenceGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/SequenceGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class SequenceGestureTests: XCTestCase { @GestureState var gestureState = CGSize.zero diff --git a/Tests/ViewInspectorTests/Gestures/SimultaneousGestureChildrenTests.swift b/Tests/ViewInspectorTests/Gestures/SimultaneousGestureChildrenTests.swift index 6648bee3..8769f867 100644 --- a/Tests/ViewInspectorTests/Gestures/SimultaneousGestureChildrenTests.swift +++ b/Tests/ViewInspectorTests/Gestures/SimultaneousGestureChildrenTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class SimultaneousGestureChildrenTests: XCTestCase { typealias GUT = SimultaneousGesture diff --git a/Tests/ViewInspectorTests/Gestures/SimultaneousGestureTests.swift b/Tests/ViewInspectorTests/Gestures/SimultaneousGestureTests.swift index fb9cd3c8..36c92942 100644 --- a/Tests/ViewInspectorTests/Gestures/SimultaneousGestureTests.swift +++ b/Tests/ViewInspectorTests/Gestures/SimultaneousGestureTests.swift @@ -7,6 +7,7 @@ import Combine @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class SimultaneousGestureTests: XCTestCase { @GestureState var gestureState = CGSize.zero diff --git a/Tests/ViewInspectorTests/InspectorTests.swift b/Tests/ViewInspectorTests/InspectorTests.swift index 83a3404f..7b277796 100644 --- a/Tests/ViewInspectorTests/InspectorTests.swift +++ b/Tests/ViewInspectorTests/InspectorTests.swift @@ -71,6 +71,44 @@ final class InspectorTests: XCTestCase { XCTAssertEqual(Inspector.print(sut), str) } + func testPrintClassHierarchy() { + let sut = TestDerivedClass() + let str = """ + TestDerivedClass + reference: Optional = nil + value: Int = 5 + + """ + XCTAssertEqual(Inspector.print(sut), str) + } + + func testPrintCyclicReferences() { + let obj1 = TestBaseClass() + obj1.reference = obj1 + let obj2 = TestBaseClass(), obj3 = TestBaseClass(), obj4 = TestBaseClass() + obj2.reference = obj3 + obj3.reference = obj4 + obj4.reference = obj2 + let str1 = """ + TestBaseClass + reference: Optional + some: TestBaseClass = { cyclic reference } + + """ + let str2 = """ + TestBaseClass + reference: Optional + some: TestBaseClass + reference: Optional + some: TestBaseClass + reference: Optional + some: TestBaseClass = { cyclic reference } + + """ + XCTAssertEqual(Inspector.print(obj1), str1) + XCTAssertEqual(Inspector.print(obj2), str2) + } + func testTupleView() throws { let view = HStack { Text(""); Text("") } let content = try Inspector.attribute(path: "_tree|content", value: view) @@ -262,3 +300,10 @@ private struct TestPrintView: View, Inspectable { Text(str[0]) } } + +private class TestBaseClass { + weak var reference: AnyObject? +} +private final class TestDerivedClass: TestBaseClass { + var value: Int = 5 +} diff --git a/Tests/ViewInspectorTests/SwiftUI/ActionSheetTests.swift b/Tests/ViewInspectorTests/SwiftUI/ActionSheetTests.swift index 97094a62..3b411cee 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ActionSheetTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ActionSheetTests.swift @@ -24,7 +24,7 @@ final class ActionSheetTests: XCTestCase { XCTAssertThrows(try sut.inspect().emptyView().actionSheet(), """ Please refer to the Guide for inspecting the ActionSheet: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#actionsheet + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#actionsheet """) } @@ -134,6 +134,27 @@ final class ActionSheetTests: XCTestCase { XCTAssertNil(binding.wrappedValue) } + func testDismiss() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().actionSheet2(isPresented: binding) { + ActionSheet(title: Text("abc")) + } + XCTAssertTrue(binding.wrappedValue) + try sut.inspect().actionSheet().dismiss() + XCTAssertFalse(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().actionSheet(), "View for ActionSheet is absent") + } + + func testDismissForItemVersion() throws { + let binding = Binding(wrappedValue: 6) + let sut = EmptyView().actionSheet2(item: binding) { value in + ActionSheet(title: Text("\(value)")) + } + try sut.inspect().emptyView().actionSheet().dismiss() + XCTAssertNil(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().actionSheet(), "View for ActionSheet is absent") + } + func testMultipleSheetsInspection() throws { let binding1 = Binding(wrappedValue: true) let binding2 = Binding(wrappedValue: true) @@ -172,15 +193,19 @@ final class ActionSheetTests: XCTestCase { XCTAssertEqual(try sut.inspect().find(text: "button_1_1").pathToRoot, "view(ActionSheetFindTestView.self).hStack().emptyView(0).actionSheet().button(1).labelView()") // 2 - XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, - "Search did not find a match") + let noMatchMessage: String + if #available(iOS 13.2, tvOS 13.2, macOS 10.17, *) { + noMatchMessage = "Search did not find a match" + } else { + noMatchMessage = "Search did not find a match. Possible blockers: ActionSheet, ActionSheet" + } + XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, noMatchMessage) // 3 XCTAssertEqual(try sut.inspect().find(text: "title_3").pathToRoot, "view(ActionSheetFindTestView.self).hStack().emptyView(0).actionSheet(1).title()") - XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, - "Search did not find a match") + XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, noMatchMessage) XCTAssertEqual(try sut.inspect().find(text: "button_3_0").pathToRoot, "view(ActionSheetFindTestView.self).hStack().emptyView(0).actionSheet(1).button(0).labelView()") } @@ -190,35 +215,36 @@ final class ActionSheetTests: XCTestCase { private extension View { func actionSheet2(isPresented: Binding, content: @escaping () -> ActionSheet) -> some View { - return self.modifier(InspectableActionSheet(isPresented: isPresented, sheetBuilder: content)) + return self.modifier(InspectableActionSheet(isPresented: isPresented, popupBuilder: content)) } func actionSheet2(item: Binding, content: @escaping (Item) -> ActionSheet ) -> some View where Item: Identifiable { - return self.modifier(InspectableActionSheetWithItem(item: item, sheetBuilder: content)) + return self.modifier(InspectableActionSheetWithItem(item: item, popupBuilder: content)) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableActionSheet: ViewModifier, ActionSheetProvider { - +private struct InspectableActionSheet: ViewModifier, PopupPresenter { let isPresented: Binding - let sheetBuilder: () -> ActionSheet + let popupBuilder: () -> ActionSheet + let onDismiss: (() -> Void)? = nil func body(content: Self.Content) -> some View { - content.actionSheet(isPresented: isPresented, content: sheetBuilder) + content.actionSheet(isPresented: isPresented, content: popupBuilder) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableActionSheetWithItem: ViewModifier, ActionSheetItemProvider { +private struct InspectableActionSheetWithItem: ViewModifier, ItemPopupPresenter { let item: Binding - let sheetBuilder: (Item) -> ActionSheet + let popupBuilder: (Item) -> ActionSheet + let onDismiss: (() -> Void)? = nil func body(content: Self.Content) -> some View { - content.actionSheet(item: item, content: sheetBuilder) + content.actionSheet(item: item, content: popupBuilder) } } diff --git a/Tests/ViewInspectorTests/SwiftUI/AlertTests.swift b/Tests/ViewInspectorTests/SwiftUI/AlertTests.swift index 171b5bdc..c0fad9fa 100644 --- a/Tests/ViewInspectorTests/SwiftUI/AlertTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/AlertTests.swift @@ -3,7 +3,7 @@ import SwiftUI @testable import ViewInspector @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -final class AlertTests: XCTestCase { +final class DeprecatedAlertTests: XCTestCase { func testInspectionNotBlocked() throws { let binding = Binding(wrappedValue: true) @@ -23,7 +23,7 @@ final class AlertTests: XCTestCase { XCTAssertThrows(try sut.inspect().emptyView().alert(), """ Please refer to the Guide for inspecting the Alert: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#alert-sheet-and-actionsheet + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#alert """) } @@ -58,7 +58,7 @@ final class AlertTests: XCTestCase { Alert(title: Text("abc"), message: Text("123"), dismissButton: nil) } let message = try sut.inspect().emptyView().alert().message() - XCTAssertEqual(try message.string(), "123") + XCTAssertEqual(try message.text().string(), "123") XCTAssertEqual(message.pathToRoot, "emptyView().alert().message()") } @@ -178,6 +178,27 @@ final class AlertTests: XCTestCase { XCTAssertNil(binding.wrappedValue) } + func testDismiss() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().alert2(isPresented: binding) { + Alert(title: Text("abc")) + } + XCTAssertTrue(binding.wrappedValue) + try sut.inspect().alert().dismiss() + XCTAssertFalse(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().alert(), "View for Alert is absent") + } + + func testDismissForItemVersion() throws { + let binding = Binding(wrappedValue: 6) + let sut = EmptyView().alert2(item: binding) { value in + Alert(title: Text("\(value)")) + } + try sut.inspect().emptyView().alert().dismiss() + XCTAssertNil(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().alert(), "View for Alert is absent") + } + func testMultipleAlertsInspection() throws { let binding1 = Binding(wrappedValue: true) let binding2 = Binding(wrappedValue: true) @@ -207,24 +228,65 @@ final class AlertTests: XCTestCase { XCTAssertEqual(try sut.inspect().find(text: "title_1").pathToRoot, "view(AlertFindTestView.self).hStack().emptyView(0).alert().title()") XCTAssertEqual(try sut.inspect().find(text: "message_1").pathToRoot, - "view(AlertFindTestView.self).hStack().emptyView(0).alert().message()") + "view(AlertFindTestView.self).hStack().emptyView(0).alert().message().text()") XCTAssertEqual(try sut.inspect().find(text: "primary_1").pathToRoot, "view(AlertFindTestView.self).hStack().emptyView(0).alert().primaryButton().labelView()") XCTAssertEqual(try sut.inspect().find(text: "secondary_1").pathToRoot, "view(AlertFindTestView.self).hStack().emptyView(0).alert().secondaryButton().labelView()") // 2 - XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, - "Search did not find a match") + let noMatchMessage: String + if #available(iOS 13.2, tvOS 13.2, macOS 10.17, *) { + noMatchMessage = "Search did not find a match" + } else { + noMatchMessage = "Search did not find a match. Possible blockers: Alert, Alert" + } + XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, noMatchMessage) // 3 XCTAssertEqual(try sut.inspect().find(text: "title_3").pathToRoot, "view(AlertFindTestView.self).hStack().emptyView(0).alert(1).title()") - XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, - "Search did not find a match") + XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, noMatchMessage) XCTAssertEqual(try sut.inspect().find(text: "primary_3").pathToRoot, "view(AlertFindTestView.self).hStack().emptyView(0).alert(1).primaryButton().labelView()") } } + +#if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +final class AlertIOS15Tests: XCTestCase { + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + private func sutIOS15(binding: Binding) -> some View { + let param: String? = "abc" + return EmptyView().alert("Title", isPresented: binding, presenting: param, + actions: { param in + Button(role: .destructive) { } label: { Text(param) } + Button("Second") { } + }, message: { param in + HStack { Text("Message: \(param)") } + }) + } + + func testAlertInspectioniOS15() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = sutIOS15(binding: binding) + let alert = try sut.inspect().alert() + XCTAssertEqual(try alert.title().string(), "Title") + let message = try alert.message().hStack().text(0) + XCTAssertEqual(try message.string(), "Message: abc") + XCTAssertEqual(message.pathToRoot, + "alert().message().hStack().text(0)") + let secondButtonLabel = try alert.actions().button(1).labelView().text() + XCTAssertEqual(try secondButtonLabel.string(), "Second") + XCTAssertEqual(secondButtonLabel.pathToRoot, + "alert().actions().button(1).labelView().text()") + let searchLabel = try sut.inspect().find(button: "Second") + XCTAssertEqual(searchLabel.pathToRoot, + "emptyView().alert().actions().button(1)") + } +} +#endif extension Int: Identifiable { public var id: Int { self } @@ -238,35 +300,36 @@ extension String: Identifiable { private extension View { func alert2(isPresented: Binding, content: @escaping () -> Alert) -> some View { - return self.modifier(InspectableAlert(isPresented: isPresented, alertBuilder: content)) + return self.modifier(InspectableAlert(isPresented: isPresented, popupBuilder: content)) } func alert2(item: Binding, content: @escaping (Item) -> Alert ) -> some View where Item: Identifiable { - return self.modifier(InspectableAlertWithItem(item: item, alertBuilder: content)) + return self.modifier(InspectableAlertWithItem(item: item, popupBuilder: content)) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableAlert: ViewModifier, AlertProvider { +private struct InspectableAlert: ViewModifier, PopupPresenter { let isPresented: Binding - let alertBuilder: () -> Alert + let popupBuilder: () -> Alert + let onDismiss: (() -> Void)? = nil func body(content: Self.Content) -> some View { - content.alert(isPresented: isPresented, content: alertBuilder) + content.alert(isPresented: isPresented, content: popupBuilder) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableAlertWithItem: ViewModifier, AlertItemProvider { - +private struct InspectableAlertWithItem: ViewModifier, ItemPopupPresenter { let item: Binding - let alertBuilder: (Item) -> Alert + let popupBuilder: (Item) -> Alert + let onDismiss: (() -> Void)? = nil func body(content: Self.Content) -> some View { - content.alert(item: item, content: alertBuilder) + content.alert(item: item, content: popupBuilder) } } diff --git a/Tests/ViewInspectorTests/SwiftUI/ButtonTests.swift b/Tests/ViewInspectorTests/SwiftUI/ButtonTests.swift index abba27ea..2bfe3097 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ButtonTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ButtonTests.swift @@ -71,6 +71,18 @@ final class ButtonTests: XCTestCase { "Button is unresponsive: it is disabled") wait(for: [exp], timeout: 0.5) } + + #if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 + func testButtonRole() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) else { return } + let sut1 = Button(role: .cancel, action: { }, label: { Text("") }) + let sut2 = Button(role: .destructive, action: { }, label: { Text("") }) + let sut3 = Button(action: { }, label: { Text("") }) + XCTAssertEqual(try sut1.inspect().button().role(), .cancel) + XCTAssertEqual(try sut2.inspect().button().role(), .destructive) + XCTAssertNil(try sut3.inspect().button().role()) + } + #endif } // MARK: - View Modifiers diff --git a/Tests/ViewInspectorTests/SwiftUI/ColorPickerTests.swift b/Tests/ViewInspectorTests/SwiftUI/ColorPickerTests.swift index 74fbb1e2..2b8a5088 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ColorPickerTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ColorPickerTests.swift @@ -2,25 +2,27 @@ import XCTest import SwiftUI @testable import ViewInspector +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class ColorPickerTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: .test) XCTAssertNoThrow(try ColorPicker("Test", selection: binding).inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: .test) let view = AnyView(ColorPicker("Test", selection: binding)) XCTAssertNoThrow(try view.inspect().anyView().colorPicker()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: .test) let view = HStack { Text("") @@ -31,7 +33,7 @@ final class ColorPickerTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: .test) let view = Group { ColorPicker(selection: binding, label: { HStack { Text("abc") } @@ -43,7 +45,7 @@ final class ColorPickerTests: XCTestCase { } func testLabelInspection() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: .red) let sut = ColorPicker(selection: binding, label: { HStack { Text("abc") } @@ -53,7 +55,7 @@ final class ColorPickerTests: XCTestCase { } func testColorSelection() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let cgColor = CGColor(red: 0.5, green: 0.2, blue: 0.7, alpha: 0.1) let binding1 = Binding(wrappedValue: cgColor) @@ -70,7 +72,7 @@ final class ColorPickerTests: XCTestCase { } func testColorSelectionWhenDisabled() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let cgColor = CGColor(red: 0.5, green: 0.2, blue: 0.7, alpha: 0.1) let binding1 = Binding(wrappedValue: cgColor) @@ -89,7 +91,7 @@ final class ColorPickerTests: XCTestCase { } func testRGBA() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } #if os(macOS) XCTAssertNotEqual(NSColor.red.rgba(), Color.red.rgba()) #else @@ -139,7 +141,9 @@ private extension UIColor { @available(tvOS, unavailable) private extension Color { @available(tvOS 14.0, *) + @available(watchOS 7.0, *) func rgba() -> ViewType.ColorPicker.RGBA { return .init(color: self) } } +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/ConditionalContentTests.swift b/Tests/ViewInspectorTests/SwiftUI/ConditionalContentTests.swift index d5227121..f6869c51 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ConditionalContentTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ConditionalContentTests.swift @@ -15,9 +15,18 @@ final class ConditionalContentTests: XCTestCase { } func testResetsModifiers() throws { - let view = ConditionalView().padding() + let view = ConditionalView().padding().offset() let sut = try view.inspect().view(ConditionalView.self).group() - XCTAssertEqual(sut.content.medium.viewModifiers.count, 0) + XCTAssertEqual(sut.content.medium.viewModifiers.count, 1) + let text = try sut.text(0) + XCTAssertEqual(text.content.medium.viewModifiers.count, 0) + } + + func testRetainsModifiers() throws { + let sut = ConditionalViewWithModifier(value: true) + let text = try sut.inspect().text() + XCTAssertEqual(try text.string(), "True") + XCTAssertEqual(try text.padding(), EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) } } @@ -29,10 +38,29 @@ private struct ConditionalView: View, Inspectable { Group { if viewModel.flag { Text("Text") } else { Image("Image") } - } + }.padding(8) } class ViewModel: ObservableObject { @Published var flag: Bool = true } } + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private struct ConditionalViewWithModifier: View, Inspectable { + + let value: Bool + + var body: some View { + content + .padding(8) + } + + @ViewBuilder private var content: some View { + if value { + Text("True") + } else { + Text("False") + } + } +} diff --git a/Tests/ViewInspectorTests/SwiftUI/ConfirmationDialogTests.swift b/Tests/ViewInspectorTests/SwiftUI/ConfirmationDialogTests.swift new file mode 100644 index 00000000..ce47dd2a --- /dev/null +++ b/Tests/ViewInspectorTests/SwiftUI/ConfirmationDialogTests.swift @@ -0,0 +1,124 @@ +import XCTest +import SwiftUI +@testable import ViewInspector + +#if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +final class ConfirmationDialogTests: XCTestCase { + + func testInspectionNotBlocked() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog(Text("title"), isPresented: binding, actions: { EmptyView() }) + XCTAssertNoThrow(try sut.inspect().emptyView()) + } + + func testInspectionErrorNoModifier() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().offset() + XCTAssertThrows(try sut.inspect().emptyView().confirmationDialog(), + "EmptyView does not have 'confirmationDialog' modifier") + } + + func testInspectionErrorWhenNotPresented() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: false) + let sut = EmptyView().confirmationDialog(Text("title"), isPresented: binding, actions: { EmptyView() }) + XCTAssertThrows(try sut.inspect().emptyView().confirmationDialog(), + "View for ConfirmationDialog is absent") + } + + func testSimpleUnwrap() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog(Text("title"), isPresented: binding, actions: { EmptyView() }) + XCTAssertEqual(try sut.inspect().emptyView().confirmationDialog().pathToRoot, + "emptyView().confirmationDialog()") + } + + func testTitleVisibility() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog( + Text("abc"), isPresented: binding, + titleVisibility: .visible, actions: { EmptyView() }) + let visibility = try sut.inspect().emptyView().confirmationDialog().titleVisibility() + XCTAssertEqual(visibility, .visible) + } + + func testTitleInspection() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog(Text("abc"), isPresented: binding, actions: { EmptyView() }) + let title = try sut.inspect().emptyView().confirmationDialog().title() + XCTAssertEqual(try title.string(), "abc") + XCTAssertEqual(title.pathToRoot, "emptyView().confirmationDialog().title()") + } + + func testMessageInspection() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog( + Text("title"), isPresented: binding, actions: { EmptyView() }, + message: { AnyView(Text("abc")) }) + let message = try sut.inspect().emptyView().confirmationDialog().message().anyView().text() + XCTAssertEqual(try message.string(), "abc") + XCTAssertEqual(message.pathToRoot, + "emptyView().confirmationDialog().message().anyView().text()") + } + + func testActionsInspection() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog( + Text(""), isPresented: binding, presenting: "abc") { AnyView(Text($0)) } + let message = try sut.inspect().emptyView().confirmationDialog().actions().anyView().text() + XCTAssertEqual(try message.string(), "abc") + XCTAssertEqual(message.pathToRoot, + "emptyView().confirmationDialog().actions().anyView().text()") + } + + func testDismiss() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().confirmationDialog( + Text("abc"), isPresented: binding, actions: { EmptyView() }) + try sut.inspect().emptyView().confirmationDialog().dismiss() + XCTAssertThrows(try sut.inspect().emptyView().confirmationDialog(), + "View for ConfirmationDialog is absent") + } + + func testSearch() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let binding = Binding(wrappedValue: true) + let sut = Group { + EmptyView() + Text("") + .confirmationDialog(Text("1"), isPresented: binding, + actions: { EmptyView() }, + message: { EmptyView(); Text("2") }) + .padding() + .confirmationDialog(Text("3"), isPresented: binding, + actions: { EmptyView(); Text("4") }) + } + XCTAssertEqual(try sut.inspect().find(text: "1").pathToRoot, + "group().text(1).confirmationDialog().title()") + XCTAssertEqual(try sut.inspect().find(text: "2").pathToRoot, + "group().text(1).confirmationDialog().message().text(1)") + XCTAssertEqual(try sut.inspect().find(text: "3").pathToRoot, + "group().text(1).confirmationDialog(1).title()") + XCTAssertEqual(try sut.inspect().find(text: "4").pathToRoot, + "group().text(1).confirmationDialog(1).actions().text(1)") + } +} +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift index 829ca765..9d4172e4 100644 --- a/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift @@ -4,7 +4,7 @@ import SwiftUI @testable import ViewInspector -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) final class CustomViewTests: XCTestCase { func testLocalStateChanges() throws { @@ -66,6 +66,7 @@ final class CustomViewTests: XCTestCase { wait(for: [exp], timeout: 0.1) } + @available(watchOS, deprecated: 7.0) func testExtractionOfTestViewRepresentable() throws { let view = AnyView(TestViewRepresentable()) let sut = try view.inspect().anyView().view(TestViewRepresentable.self) @@ -73,22 +74,42 @@ final class CustomViewTests: XCTestCase { #if os(macOS) XCTAssertThrows(try sut.hStack(), "Please use `.actualView().nsView()` for inspecting the contents of NSViewRepresentable") + // Needs view hosting: + XCTAssertThrows(try sut.actualView().nsView(), + "View for TestViewRepresentable is absent") + #elseif os(watchOS) + XCTAssertThrows(try sut.hStack(), + "Please use `.actualView().interfaceObject()` for inspecting the contents of WKInterfaceObjectRepresentable") + // Needs view hosting: + XCTAssertThrows(try sut.actualView().interfaceObject(), + "View for TestViewRepresentable is absent") #else XCTAssertThrows(try sut.hStack(), "Please use `.actualView().uiView()` for inspecting the contents of UIViewRepresentable") + // Needs view hosting: + XCTAssertThrows(try sut.actualView().uiView(), + "View for TestViewRepresentable is absent") #endif } func testExtractionOfViewControllerRepresentable() throws { + #if !os(watchOS) let view = AnyView(TestViewControllerRepresentable()) let sut = try view.inspect().anyView().view(TestViewControllerRepresentable.self) XCTAssertNoThrow(try sut.actualView()) #if os(macOS) XCTAssertThrows(try sut.hStack(), "Please use `.actualView().viewController()` for inspecting the contents of NSViewControllerRepresentable") + // Needs view hosting: + XCTAssertThrows(try sut.actualView().viewController(), + "View for TestViewControllerRepresentable is absent") #else XCTAssertThrows(try sut.hStack(), "Please use `.actualView().viewController()` for inspecting the contents of UIViewControllerRepresentable") + // Needs view hosting: + XCTAssertThrows(try sut.actualView().viewController(), + "View for TestViewControllerRepresentable is absent") + #endif #endif } @@ -255,7 +276,7 @@ private struct TestViewControllerRepresentable: NSViewControllerRepresentable, I func updateNSViewController(_ uiViewController: NSViewControllerType, context: Context) { } } -#else +#elseif os(iOS) || os(tvOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private struct TestViewRepresentable: UIViewRepresentable, Inspectable { @@ -281,6 +302,20 @@ private struct TestViewControllerRepresentable: UIViewControllerRepresentable, I func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } +#elseif os(watchOS) + +@available(watchOS, deprecated: 7.0) +private struct TestViewRepresentable: WKInterfaceObjectRepresentable, Inspectable { + + typealias Context = WKInterfaceObjectRepresentableContext + func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject { + return WKInterfaceMap() + } + + func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) { + } +} + #endif // MARK: - Misc diff --git a/Tests/ViewInspectorTests/SwiftUI/DatePickerTests.swift b/Tests/ViewInspectorTests/SwiftUI/DatePickerTests.swift index b5e834fc..383e2c9a 100644 --- a/Tests/ViewInspectorTests/SwiftUI/DatePickerTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/DatePickerTests.swift @@ -4,6 +4,7 @@ import SwiftUI @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class DatePickerTests: XCTestCase { class StateObject: ObservableObject { @@ -74,6 +75,7 @@ final class DatePickerTests: XCTestCase { @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class GlobalModifiersForDatePicker: XCTestCase { func testDatePickerStyle() throws { diff --git a/Tests/ViewInspectorTests/SwiftUI/DisclosureGroupTests.swift b/Tests/ViewInspectorTests/SwiftUI/DisclosureGroupTests.swift index c0b15600..7f674b9c 100644 --- a/Tests/ViewInspectorTests/SwiftUI/DisclosureGroupTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/DisclosureGroupTests.swift @@ -2,8 +2,10 @@ import XCTest import SwiftUI @testable import ViewInspector +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class DisclosureGroupTests: XCTestCase { func testInspect() throws { @@ -99,6 +101,7 @@ final class DisclosureGroupTests: XCTestCase { @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private struct TestViewState: View, Inspectable { @ObservedObject var state = ExpansionState() @@ -116,6 +119,7 @@ private struct TestViewState: View, Inspectable { @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private struct TestViewBinding: View, Inspectable { @Binding var expanded: Bool = false @@ -131,3 +135,4 @@ private struct TestViewBinding: View, Inspectable { }, label: { EmptyView() }) } } +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/EnvironmentReaderViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/EnvironmentReaderViewTests.swift index 54c3a470..b35cef07 100644 --- a/Tests/ViewInspectorTests/SwiftUI/EnvironmentReaderViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/EnvironmentReaderViewTests.swift @@ -3,20 +3,35 @@ import SwiftUI @testable import ViewInspector #if os(iOS) || os(tvOS) -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) final class EnvironmentReaderViewTests: XCTestCase { + func skipForiOS15(file: StaticString = #file, line: UInt = #line) throws { + if #available(iOS 15.0, tvOS 15.0, *) { + throw XCTSkip("Not relevant for iOS 15", file: file, line: line) + } + } + + func testUnaryViewAdaptor() throws { + guard #available(iOS 15.0, tvOS 15.0, *) else { return } + let sut = EmptyView() + .navigationBarItems(trailing: Text("abc")) + XCTAssertNoThrow(try sut.inspect().emptyView()) + } + func testIncorrectUnwrap() throws { + try skipForiOS15() let view = NavigationView { List { Text("") } .navigationBarItems(trailing: Text("")) } XCTAssertThrows( - try view.inspect().navigationView().list(0), + try view.inspect().navigationView().list(0).text(0), "Please insert '.navigationBarItems()' before list(0) for unwrapping the underlying view hierarchy.") } func testUnknownHierarchyTypeUnwrap() throws { + try skipForiOS15() let view = NavigationView { List { Text("") } .navigationBarItems(trailing: Text("")) @@ -27,6 +42,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testKnownHierarchyTypeUnwrap() throws { + try skipForiOS15() let string = "abc" let view = NavigationView { List { Text(string) } @@ -39,6 +55,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testSearchBlocker() throws { + try skipForiOS15() let view = AnyView(NavigationView { Text("abc") .navigationBarItems(trailing: Text("")) @@ -49,6 +66,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testRetainsModifiers() throws { + try skipForiOS15() let view = NavigationView { Text("") .padding() @@ -62,6 +80,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testMissingModifier() throws { + try skipForiOS15() let sut = EmptyView().padding() XCTAssertThrows( try sut.inspect().navigationBarItems(), @@ -69,6 +88,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testCustomViewUnwrapStepOne() throws { + try skipForiOS15() let sut = TestView() let exp = sut.inspection.inspect { view in XCTAssertThrows(try view.vStack(), @@ -79,6 +99,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testCustomViewUnwrapStepTwo() throws { + try skipForiOS15() let sut = TestView() let exp = sut.inspection.inspect { view in XCTAssertThrows(try view.navigationBarItems().vStack(), @@ -89,6 +110,7 @@ final class EnvironmentReaderViewTests: XCTestCase { } func testCustomViewUnwrapStepThree() throws { + try skipForiOS15() let sut = TestView() let exp = sut.inspection.inspect { view in typealias WrappedView = VStack diff --git a/Tests/ViewInspectorTests/SwiftUI/ForEachTests.swift b/Tests/ViewInspectorTests/SwiftUI/ForEachTests.swift index cc74f505..deefe474 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ForEachTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ForEachTests.swift @@ -83,7 +83,7 @@ final class ForEachTests: XCTestCase { func testRangeBased() throws { let range = 0..<5 - let view = ForEach(range) { Text(verbatim: "\($0)") } + let view = ForEach(0..<5) { Text(verbatim: "\($0)") } let sut = try view.inspect().forEach() XCTAssertEqual(sut.count, 5) @@ -134,7 +134,7 @@ final class ForEachTests: XCTestCase { #if os(macOS) func testOnInsert() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let exp = XCTestExpectation(description: #function) let sut = ForEach([0, 1, 3], id: \.self) { id in Text("\(id)") } diff --git a/Tests/ViewInspectorTests/SwiftUI/FullScreenCoverTests.swift b/Tests/ViewInspectorTests/SwiftUI/FullScreenCoverTests.swift new file mode 100644 index 00000000..596d75a7 --- /dev/null +++ b/Tests/ViewInspectorTests/SwiftUI/FullScreenCoverTests.swift @@ -0,0 +1,234 @@ +import XCTest +import SwiftUI +@testable import ViewInspector + +#if !os(macOS) +final class FullScreenCoverTests: XCTestCase { + + func testFullScreenCover() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().fullScreenCover(isPresented: binding) { Text("") } + XCTAssertNoThrow(try sut.inspect().emptyView()) + } + + func testInspectionErrorNoModifier() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let sut = EmptyView().offset() + XCTAssertThrows(try sut.inspect().emptyView().fullScreenCover(), + "EmptyView does not have 'fullScreenCover' modifier") + } + + func testInspectionErrorCustomModifierRequired() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().fullScreenCover(isPresented: binding) { Text("") } + XCTAssertThrows(try sut.inspect().emptyView().fullScreenCover(), + """ + Please refer to the Guide for inspecting the FullScreenCover: \ + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#fullscreencover + """) + } + + func testInspectionErrorFullScreenCoverNotPresented() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: false) + let sut = EmptyView().fullScreenCover2(isPresented: binding) { Text("") } + XCTAssertThrows(try sut.inspect().emptyView().fullScreenCover(), + "View for FullScreenCover is absent") + } + + func testInspectionErrorFullScreenCoverWithItemNotPresented() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: nil) + let sut = EmptyView().fullScreenCover2(item: binding) { Text("\($0)") } + XCTAssertThrows(try sut.inspect().emptyView().fullScreenCover(), + "View for FullScreenCover is absent") + } + + func testContentInspection() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().fullScreenCover2(isPresented: binding) { + Text("abc") + } + let title = try sut.inspect().emptyView().fullScreenCover().text() + XCTAssertEqual(try title.string(), "abc") + XCTAssertEqual(title.pathToRoot, "emptyView().fullScreenCover().text()") + } + + func testContentInteraction() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = EmptyView().fullScreenCover2(isPresented: binding) { + Text("abc") + Button("xyz", action: { binding.wrappedValue = false }) + } + let button = try sut.inspect().emptyView().fullScreenCover().button(1) + try button.tap() + XCTAssertFalse(binding.wrappedValue) + XCTAssertEqual(button.pathToRoot, "emptyView().fullScreenCover().button(1)") + } + + func testDismiss() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let exp = XCTestExpectation(description: #function) + let binding = Binding(wrappedValue: true) + let sut = EmptyView().fullScreenCover2(isPresented: binding, onDismiss: { + exp.fulfill() + }, content: { Text("") }) + XCTAssertTrue(binding.wrappedValue) + try sut.inspect().fullScreenCover().dismiss() + XCTAssertFalse(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().fullScreenCover(), "View for FullScreenCover is absent") + wait(for: [exp], timeout: 0.1) + } + + func testDismissForItemVersion() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: 6) + let sut = EmptyView().fullScreenCover2(item: binding) { Text("\($0)") } + let fullScreenCover = try sut.inspect().fullScreenCover() + XCTAssertEqual(try fullScreenCover.text().string(), "6") + XCTAssertEqual(binding.wrappedValue, 6) + try fullScreenCover.dismiss() + XCTAssertNil(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().fullScreenCover(), "View for FullScreenCover is absent") + } + + func testMultipleFullScreenCoversInspection() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding1 = Binding(wrappedValue: true) + let binding2 = Binding(wrappedValue: true) + let binding3 = Binding(wrappedValue: true) + let sut = FullScreenCoverFindTestView( + fullScreenCover1: binding1, fullScreenCover2: binding2, fullScreenCover3: binding3) + let title1 = try sut.inspect().hStack().emptyView(0).fullScreenCover().text(0) + XCTAssertEqual(try title1.string(), "title_1") + XCTAssertEqual(title1.pathToRoot, + "view(FullScreenCoverFindTestView.self).hStack().emptyView(0).fullScreenCover().text(0)") + let title2 = try sut.inspect().hStack().emptyView(0).fullScreenCover(1).text(0) + XCTAssertEqual(try title2.string(), "title_3") + XCTAssertEqual(title2.pathToRoot, + "view(FullScreenCoverFindTestView.self).hStack().emptyView(0).fullScreenCover(1).text(0)") + + XCTAssertEqual(try sut.inspect().find(ViewType.FullScreenCover.self).text(0).string(), "title_1") + binding1.wrappedValue = false + XCTAssertEqual(try sut.inspect().find(ViewType.FullScreenCover.self).text(0).string(), "title_3") + binding3.wrappedValue = false + XCTAssertThrows(try sut.inspect().find(ViewType.FullScreenCover.self), + "Search did not find a match") + } + + func testFindAndPathToRoots() throws { + guard #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) else { return } + let binding = Binding(wrappedValue: true) + let sut = FullScreenCoverFindTestView( + fullScreenCover1: binding, fullScreenCover2: binding, fullScreenCover3: binding) + + // 1 + XCTAssertEqual(try sut.inspect().find(text: "title_1").pathToRoot, + "view(FullScreenCoverFindTestView.self).hStack().emptyView(0).sheet().text(0)") + XCTAssertEqual(try sut.inspect().find(text: "button_1").pathToRoot, + """ + view(FullScreenCoverFindTestView.self).hStack().emptyView(0)\ + .sheet().button(1).labelView().text() + """) + // 2 + XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, + "Search did not find a match") + + // 3 + XCTAssertEqual(try sut.inspect().find(text: "title_3").pathToRoot, + "view(FullScreenCoverFindTestView.self).hStack().emptyView(0).sheet(1).text(0)") + + XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, + "Search did not find a match") + XCTAssertEqual(try sut.inspect().find(text: "button_3").pathToRoot, + """ + view(FullScreenCoverFindTestView.self).hStack().emptyView(0)\ + .sheet(1).button(1).labelView().text() + """) + } +} + +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(macOS, unavailable) +private extension View { + func fullScreenCover2(isPresented: Binding, + onDismiss: (() -> Void)? = nil, + @ViewBuilder content: @escaping () -> FullScreenCover + ) -> some View where FullScreenCover: View { + return self.modifier(InspectableFullScreenCover( + isPresented: isPresented, onDismiss: onDismiss, popupBuilder: content)) + } + + func fullScreenCover2(item: Binding, + onDismiss: (() -> Void)? = nil, + content: @escaping (Item) -> FullScreenCover + ) -> some View where Item: Identifiable, FullScreenCover: View { + return self.modifier(InspectableFullScreenCoverWithItem( + item: item, onDismiss: onDismiss, popupBuilder: content)) + } +} + +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(macOS, unavailable) +private struct InspectableFullScreenCover: ViewModifier, PopupPresenter +where FullScreenCover: View { + + let isPresented: Binding + let onDismiss: (() -> Void)? + let popupBuilder: () -> FullScreenCover + + func body(content: Self.Content) -> some View { + content.fullScreenCover(isPresented: isPresented, onDismiss: onDismiss, content: popupBuilder) + } +} + +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(macOS, unavailable) +private struct InspectableFullScreenCoverWithItem: ViewModifier, ItemPopupPresenter +where Item: Identifiable, FullScreenCover: View { + + let item: Binding + let onDismiss: (() -> Void)? + let popupBuilder: (Item) -> FullScreenCover + + func body(content: Self.Content) -> some View { + content.fullScreenCover(item: item, onDismiss: onDismiss, content: popupBuilder) + } +} + +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(macOS, unavailable) +private struct FullScreenCoverFindTestView: View, Inspectable { + + @Binding var isFullScreenCover1Presented = false + @Binding var isFullScreenCover2Presented = false + @Binding var isFullScreenCover3Presented = false + + init(fullScreenCover1: Binding, fullScreenCover2: Binding, fullScreenCover3: Binding) { + _isFullScreenCover1Presented = fullScreenCover1 + _isFullScreenCover2Presented = fullScreenCover2 + _isFullScreenCover3Presented = fullScreenCover3 + } + + var body: some View { + HStack { + EmptyView() + .fullScreenCover2(isPresented: $isFullScreenCover1Presented) { + Text("title_1") + Button("button_1", action: { }) + } + .fullScreenCover(isPresented: $isFullScreenCover2Presented) { + Text("title_2") + } + .fullScreenCover2(isPresented: $isFullScreenCover3Presented) { + Text("title_3") + Button("button_3", action: { }) + } + } + } +} +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/GroupBoxTests.swift b/Tests/ViewInspectorTests/SwiftUI/GroupBoxTests.swift index aa007ac8..34e1c4c0 100644 --- a/Tests/ViewInspectorTests/SwiftUI/GroupBoxTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/GroupBoxTests.swift @@ -4,10 +4,11 @@ import SwiftUI @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class GroupBoxTests: XCTestCase { func testSingleEnclosedView() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sampleView = Text("Test") let view = GroupBox { sampleView } let sut = try view.inspect().groupBox().text(0).content.view as? Text @@ -15,7 +16,7 @@ final class GroupBoxTests: XCTestCase { } func testSingleEnclosedViewIndexOutOfBounds() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sampleView = Text("Test") let view = GroupBox { sampleView } XCTAssertThrows( @@ -24,7 +25,7 @@ final class GroupBoxTests: XCTestCase { } func testMultipleEnclosedViews() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sampleView1 = Text("Test") let sampleView2 = Text("Abc") let sampleView3 = Text("XYZ") @@ -38,7 +39,7 @@ final class GroupBoxTests: XCTestCase { } func testMultipleEnclosedViewsIndexOutOfBounds() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sampleView1 = Text("Test") let sampleView2 = Text("Abc") let view = GroupBox { sampleView1; sampleView2 } @@ -48,20 +49,20 @@ final class GroupBoxTests: XCTestCase { } func testResetsModifiers() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let view = GroupBox { Text("Test") }.padding() let sut = try view.inspect().groupBox().text(0) XCTAssertEqual(sut.content.medium.viewModifiers.count, 0) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let view = AnyView(GroupBox { Text("Test") }) XCTAssertNoThrow(try view.inspect().anyView().groupBox()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let view = GroupBox { GroupBox { Text("Test") } GroupBox { Text("Test") } @@ -71,7 +72,7 @@ final class GroupBoxTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let view = AnyView(GroupBox { Text("Test") }) XCTAssertEqual(try view.inspect().find(ViewType.GroupBox.self).pathToRoot, "anyView().groupBox()") @@ -80,7 +81,7 @@ final class GroupBoxTests: XCTestCase { } func testLabelInspection() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let view = GroupBox( label: HStack { Text("abc") }, content: { Text("test") }) @@ -88,15 +89,15 @@ final class GroupBoxTests: XCTestCase { XCTAssertEqual(sut, "abc") } - #if !os(tvOS) && !os(macOS) && !targetEnvironment(macCatalyst) + #if os(iOS) || os(macOS) func testGroupBoxStyleInspection() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sut = EmptyView().groupBoxStyle(DefaultGroupBoxStyle()) XCTAssertTrue(try sut.inspect().groupBoxStyle() is DefaultGroupBoxStyle) } func testCustomGroupBoxStyleInspection() throws { - guard #available(iOS 14, *) else { return } + guard #available(iOS 14, macOS 11.0, *) else { return } let sut = TestGroupBoxStyle() XCTAssertEqual(try sut.inspect().vStack().styleConfigurationContent(0).blur().radius, 5) XCTAssertEqual(try sut.inspect().vStack().styleConfigurationLabel(1).brightness(), 3) @@ -109,8 +110,10 @@ final class GroupBoxTests: XCTestCase { #endif } +#if os(iOS) || os(macOS) @available(iOS 14.0, macOS 11.0, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private struct TestGroupBoxStyle: GroupBoxStyle { func makeBody(configuration: Configuration) -> some View { VStack { @@ -121,3 +124,4 @@ private struct TestGroupBoxStyle: GroupBoxStyle { } } } +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/HStackTests.swift b/Tests/ViewInspectorTests/SwiftUI/HStackTests.swift index fa834678..5b53c457 100644 --- a/Tests/ViewInspectorTests/SwiftUI/HStackTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/HStackTests.swift @@ -71,7 +71,7 @@ final class HStackTests: XCTestCase { } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack(spacing: 7) { Text("") } diff --git a/Tests/ViewInspectorTests/SwiftUI/ImageTests.swift b/Tests/ViewInspectorTests/SwiftUI/ImageTests.swift index d1541dd4..f53a1dc7 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ImageTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ImageTests.swift @@ -26,7 +26,7 @@ final class ImageTests: XCTestCase { } func testExternalImage() throws { - #if os(iOS) || os(tvOS) + #if !os(macOS) let sut = Image(uiImage: testImage) let image = try sut.uiImage() #else @@ -38,7 +38,7 @@ final class ImageTests: XCTestCase { func testExtractionWithModifiers() throws { let view = AnyView(imageView().resizable().interpolation(.low)) - #if os(iOS) || os(tvOS) + #if !os(macOS) let image = try view.inspect().anyView().image().actualImage().uiImage() #else let image = try view.inspect().anyView().image().actualImage().nsImage() @@ -57,7 +57,7 @@ final class ImageTests: XCTestCase { XCTAssertEqual(scale, 2.0) XCTAssertEqual(orientation, .down) XCTAssertEqual(label, "CGImage") - #if os(iOS) || os(tvOS) + #if !os(macOS) XCTAssertThrows(try image.uiImage(), "Type mismatch: CGImageProvider is not UIImage") #else XCTAssertThrows(try image.nsImage(), "Type mismatch: CGImageProvider is not NSImage") @@ -65,7 +65,7 @@ final class ImageTests: XCTestCase { } func testLabelImageText() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = Label("tx", image: "img") let text = try view.inspect().label().icon().image().labelView() XCTAssertEqual(try text.string(), "img") @@ -98,7 +98,7 @@ final class ImageTests: XCTestCase { } private func imageView() -> Image { - #if os(iOS) || os(tvOS) + #if !os(macOS) return Image(uiImage: testImage) #else return Image(nsImage: testImage) @@ -117,7 +117,19 @@ extension UIColor { } } } -#else +#elseif os(watchOS) +extension UIColor { + func image(_ size: CGSize) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, 0) + defer { + UIGraphicsEndImageContext() + } + self.setFill() + UIRectFill(.init(origin: .zero, size: size)) + return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() + } +} +#elseif os(macOS) extension NSColor { func image(_ size: CGSize) -> NSImage { let image = NSImage(size: size) @@ -134,7 +146,7 @@ extension NSImage { } #endif -#if os(iOS) || os(tvOS) +#if !os(macOS) let testColor = UIColor.red #else let testColor = NSColor.red diff --git a/Tests/ViewInspectorTests/SwiftUI/LabelTests.swift b/Tests/ViewInspectorTests/SwiftUI/LabelTests.swift index 5681b9b4..4323a508 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LabelTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LabelTests.swift @@ -6,18 +6,18 @@ import SwiftUI final class LabelTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } XCTAssertNoThrow(try Label("title", image: "image").inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(Label("title", image: "image")) XCTAssertNoThrow(try view.inspect().anyView().label()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") Label("title", image: "image") @@ -27,7 +27,7 @@ final class LabelTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Label("tx", image: "img") } XCTAssertEqual(try view.inspect().find(ViewType.Label.self).pathToRoot, "hStack().label(0)") @@ -38,7 +38,7 @@ final class LabelTests: XCTestCase { } func testTitleInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = Label(title: { HStack { Text("abc") } }, icon: { @@ -49,7 +49,7 @@ final class LabelTests: XCTestCase { } func testIconInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = Label(title: { HStack { Text("abc") } }, icon: { @@ -66,19 +66,19 @@ final class LabelTests: XCTestCase { final class GlobalModifiersForLabel: XCTestCase { func testLabelStyle() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = EmptyView().labelStyle(IconOnlyLabelStyle()) XCTAssertNoThrow(try sut.inspect().emptyView()) } func testLabelStyleInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = EmptyView().labelStyle(IconOnlyLabelStyle()) XCTAssertTrue(try sut.inspect().labelStyle() is IconOnlyLabelStyle) } func testCustomLabelStyleInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = TestLabelStyle() let title = try sut.inspect().vStack().styleConfigurationTitle(0) let icon = try sut.inspect().vStack().styleConfigurationIcon(1) @@ -95,7 +95,7 @@ final class GlobalModifiersForLabel: XCTestCase { } } -@available(iOS 14.0, macOS 11.0, tvOS 14.0, *) +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) struct TestLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { VStack { diff --git a/Tests/ViewInspectorTests/SwiftUI/LazyHGridTests.swift b/Tests/ViewInspectorTests/SwiftUI/LazyHGridTests.swift index 881463e2..833cde94 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LazyHGridTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LazyHGridTests.swift @@ -6,19 +6,19 @@ import SwiftUI final class LazyHGridTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [], content: { Text("abc") }) XCTAssertNoThrow(try view.inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(LazyHGrid(rows: [], content: { Text("abc") })) XCTAssertNoThrow(try view.inspect().anyView().lazyHGrid()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") LazyHGrid(rows: [], content: { Text("abc") }) @@ -28,7 +28,7 @@ final class LazyHGridTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { LazyHGrid(rows: [], content: { Text("abc") }) } XCTAssertEqual(try view.inspect().find(ViewType.LazyHGrid.self).pathToRoot, "hStack().lazyHGrid(0)") @@ -37,7 +37,7 @@ final class LazyHGridTests: XCTestCase { } func testContentViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [], content: { ForEach((0...10), id: \.self) { Text("\($0)") } }) @@ -46,28 +46,28 @@ final class LazyHGridTests: XCTestCase { } func testAlignmentInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [], alignment: .top) { Text("") } let sut = try view.inspect().lazyHGrid().alignment() XCTAssertEqual(sut, .top) } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [], spacing: 5) { Text("") } let sut = try view.inspect().lazyHGrid().spacing() XCTAssertEqual(sut, 5) } func testPinnedViewsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [], pinnedViews: .sectionFooters) { Text("") } let sut = try view.inspect().lazyHGrid().pinnedViews() XCTAssertEqual(sut, .sectionFooters) } func testRowsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHGrid(rows: [GridItem(.fixed(10))]) { Text("") } let sut = try view.inspect().lazyHGrid().rows() XCTAssertEqual(sut, [GridItem(.fixed(10))]) @@ -77,7 +77,7 @@ final class LazyHGridTests: XCTestCase { @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) final class GridItemTests: XCTestCase { func testEquatable() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let items = [ GridItem(.fixed(5), spacing: 40, alignment: .bottomLeading), GridItem(.adaptive(minimum: 10, maximum: 20)), diff --git a/Tests/ViewInspectorTests/SwiftUI/LazyHStackTests.swift b/Tests/ViewInspectorTests/SwiftUI/LazyHStackTests.swift index 7e9db40d..a47c7501 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LazyHStackTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LazyHStackTests.swift @@ -6,19 +6,19 @@ import SwiftUI final class LazyHStackTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHStack(content: { Text("abc") }) XCTAssertNoThrow(try view.inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(LazyHStack(content: { Text("abc") })) XCTAssertNoThrow(try view.inspect().anyView().lazyHStack()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") LazyHStack(content: { Text("abc") }) @@ -28,7 +28,7 @@ final class LazyHStackTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { LazyHStack(content: { Text("abc") }) } XCTAssertEqual(try view.inspect().find(ViewType.LazyHStack.self).pathToRoot, "hStack().lazyHStack(0)") @@ -37,7 +37,7 @@ final class LazyHStackTests: XCTestCase { } func testContentViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHStack(content: { ForEach((0...10), id: \.self) { Text("\($0)") } }) @@ -46,21 +46,21 @@ final class LazyHStackTests: XCTestCase { } func testAlignmentInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHStack(alignment: .top) { Text("") } let sut = try view.inspect().lazyHStack().alignment() XCTAssertEqual(sut, .top) } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHStack(spacing: 5) { Text("") } let sut = try view.inspect().lazyHStack().spacing() XCTAssertEqual(sut, 5) } func testPinnedViewsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyHStack(pinnedViews: .sectionFooters) { Text("") } let sut = try view.inspect().lazyHStack().pinnedViews() XCTAssertEqual(sut, .sectionFooters) diff --git a/Tests/ViewInspectorTests/SwiftUI/LazyVGridTests.swift b/Tests/ViewInspectorTests/SwiftUI/LazyVGridTests.swift index 119c356e..a10e8c17 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LazyVGridTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LazyVGridTests.swift @@ -6,19 +6,19 @@ import SwiftUI final class LazyVGridTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [], content: { Text("abc") }) XCTAssertNoThrow(try view.inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(LazyVGrid(columns: [], content: { Text("abc") })) XCTAssertNoThrow(try view.inspect().anyView().lazyVGrid()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") LazyVGrid(columns: [], content: { Text("abc") }) @@ -28,7 +28,7 @@ final class LazyVGridTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { LazyVGrid(columns: [], content: { Text("abc") }) } XCTAssertEqual(try view.inspect().find(ViewType.LazyVGrid.self).pathToRoot, "hStack().lazyVGrid(0)") @@ -37,7 +37,7 @@ final class LazyVGridTests: XCTestCase { } func testContentViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [], content: { ForEach((0...10), id: \.self) { Text("\($0)") } }) @@ -46,28 +46,28 @@ final class LazyVGridTests: XCTestCase { } func testAlignmentInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [], alignment: .leading) { Text("") } let sut = try view.inspect().lazyVGrid().alignment() XCTAssertEqual(sut, .leading) } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [], spacing: 5) { Text("") } let sut = try view.inspect().lazyVGrid().spacing() XCTAssertEqual(sut, 5) } func testPinnedViewsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [], pinnedViews: .sectionFooters) { Text("") } let sut = try view.inspect().lazyVGrid().pinnedViews() XCTAssertEqual(sut, .sectionFooters) } func testColumnsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVGrid(columns: [GridItem(.fixed(10))]) { Text("") } let sut = try view.inspect().lazyVGrid().columns() XCTAssertEqual(sut, [GridItem(.fixed(10))]) diff --git a/Tests/ViewInspectorTests/SwiftUI/LazyVStackTests.swift b/Tests/ViewInspectorTests/SwiftUI/LazyVStackTests.swift index 1f9862c7..11267b54 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LazyVStackTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LazyVStackTests.swift @@ -6,19 +6,19 @@ import SwiftUI final class LazyVStackTests: XCTestCase { func testInspect() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVStack(content: { Text("abc") }) XCTAssertNoThrow(try view.inspect()) } func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(LazyVStack(content: { Text("abc") })) XCTAssertNoThrow(try view.inspect().anyView().lazyVStack()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") LazyVStack(content: { Text("abc") }) @@ -28,7 +28,7 @@ final class LazyVStackTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { LazyVStack(content: { Text("abc") }) } XCTAssertEqual(try view.inspect().find(ViewType.LazyVStack.self).pathToRoot, "hStack().lazyVStack(0)") @@ -37,7 +37,7 @@ final class LazyVStackTests: XCTestCase { } func testContentViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVStack(content: { ForEach((0...10), id: \.self) { Text("\($0)") } }) @@ -46,21 +46,21 @@ final class LazyVStackTests: XCTestCase { } func testAlignmentInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVStack(alignment: .leading) { Text("") } let sut = try view.inspect().lazyVStack().alignment() XCTAssertEqual(sut, .leading) } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVStack(spacing: 5) { Text("") } let sut = try view.inspect().lazyVStack().spacing() XCTAssertEqual(sut, 5) } func testPinnedViewsInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = LazyVStack(pinnedViews: .sectionFooters) { Text("") } let sut = try view.inspect().lazyVStack().pinnedViews() XCTAssertEqual(sut, .sectionFooters) diff --git a/Tests/ViewInspectorTests/SwiftUI/LinkTests.swift b/Tests/ViewInspectorTests/SwiftUI/LinkTests.swift index b70b3f86..d5f160d3 100644 --- a/Tests/ViewInspectorTests/SwiftUI/LinkTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/LinkTests.swift @@ -8,13 +8,13 @@ final class LinkTests: XCTestCase { let url = URL(fileURLWithPath: "test") func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(Link("abc", destination: url)) XCTAssertNoThrow(try view.inspect().anyView().link()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") Link("abc", destination: url) @@ -24,7 +24,7 @@ final class LinkTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(Link(destination: url, label: { HStack { Text("xyz") } })) @@ -34,13 +34,13 @@ final class LinkTests: XCTestCase { } func testURLInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = Link("abc", destination: url) XCTAssertEqual(try view.inspect().link().url(), url) } func testLabelInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = Link(destination: url, label: { HStack { Text("xyz") } }) diff --git a/Tests/ViewInspectorTests/SwiftUI/MapAnnotationTests.swift b/Tests/ViewInspectorTests/SwiftUI/MapAnnotationTests.swift index 4d206c91..e92e8439 100644 --- a/Tests/ViewInspectorTests/SwiftUI/MapAnnotationTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/MapAnnotationTests.swift @@ -11,7 +11,7 @@ class MapAnnotationTests: XCTestCase { private let testAnchor = CGPoint(x: 3, y: 4) func testMapAnnotationAttributes() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let sut = MapAnnotation(coordinate: testCoordinate, anchorPoint: testAnchor) { EmptyView() Text("abc") @@ -24,21 +24,21 @@ class MapAnnotationTests: XCTestCase { } func testMapMarkerAttributes() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let sut = MapMarker(coordinate: testCoordinate, tint: .red) XCTAssertEqual(try sut.coordinate(), testCoordinate) XCTAssertEqual(try sut.tintColor(), .red) } func testMapPinAttributes() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let sut = MapPin(coordinate: testCoordinate, tint: .blue) XCTAssertEqual(try sut.coordinate(), testCoordinate) XCTAssertEqual(try sut.tintColor(), .blue) } func testExtractionFromMap() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let coord = CLLocationCoordinate2D(latitude: 1, longitude: 2) let region = Binding(wrappedValue: .init()) let items = Array(0...5) diff --git a/Tests/ViewInspectorTests/SwiftUI/MapTests.swift b/Tests/ViewInspectorTests/SwiftUI/MapTests.swift index 794c9353..f084b8fc 100644 --- a/Tests/ViewInspectorTests/SwiftUI/MapTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/MapTests.swift @@ -12,13 +12,13 @@ class MapTests: XCTestCase { latitudinalMeters: 987, longitudinalMeters: 6) func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let sut = AnyView(Map(coordinateRegion: .constant(MKCoordinateRegion()))) XCTAssertNoThrow(try sut.inspect().anyView().map()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let view = HStack { Map(coordinateRegion: .constant(MKCoordinateRegion())) Map(coordinateRegion: .constant(MKCoordinateRegion())) @@ -28,19 +28,19 @@ class MapTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let view = AnyView(Map(coordinateRegion: .constant(MKCoordinateRegion()))) XCTAssertEqual(try view.inspect().find(ViewType.Map.self).pathToRoot, "anyView().map()") } func testExtractingCoordinateRegionValue() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let sut = Map(coordinateRegion: .constant(testRegion)) XCTAssertEqual(try sut.inspect().map().coordinateRegion(), testRegion) } func testSettingCoordinateRegionValue() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: MKCoordinateRegion()) let sut = Map(coordinateRegion: binding) XCTAssertEqual(try sut.inspect().map().coordinateRegion(), MKCoordinateRegion()) @@ -50,7 +50,7 @@ class MapTests: XCTestCase { } func testErrorOnSettingCoordinateRegionWhenNonResponsive() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: MKCoordinateRegion()) let sut = Map(coordinateRegion: binding).hidden() XCTAssertEqual(try sut.inspect().map().coordinateRegion(), MKCoordinateRegion()) @@ -60,7 +60,7 @@ class MapTests: XCTestCase { } func testExtractingMapRectValue() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let rect = MKMapRect(x: 3, y: 5, width: 1, height: 8) let sut = Map(mapRect: .constant(rect), interactionModes: .all, @@ -70,7 +70,7 @@ class MapTests: XCTestCase { } func testSettingMapRectValue() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: MKMapRect()) let sut = Map(mapRect: binding, interactionModes: .all, @@ -84,7 +84,7 @@ class MapTests: XCTestCase { } func testErrorOnSettingMapRectWhenNonResponsive() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: MKMapRect()) let sut = Map(mapRect: binding, interactionModes: .all, @@ -99,7 +99,7 @@ class MapTests: XCTestCase { } func testExtractingInteractionModes() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let region = MKCoordinateRegion() let sut = Map(coordinateRegion: .constant(region), interactionModes: .pan, @@ -110,7 +110,7 @@ class MapTests: XCTestCase { } func testExtractingShowsUserLocation() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let region = MKCoordinateRegion() let sut = Map(coordinateRegion: .constant(region), interactionModes: .all, @@ -121,7 +121,7 @@ class MapTests: XCTestCase { } func testExtractingUserTrackingMode() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let region = MKCoordinateRegion() let sut = Map(coordinateRegion: .constant(region), interactionModes: .all, @@ -132,7 +132,7 @@ class MapTests: XCTestCase { } func testSettingUserTrackingMode() throws { - guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) else { return } + guard #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: MapUserTrackingMode.follow) let sut = Map(coordinateRegion: .constant(MKCoordinateRegion()), interactionModes: .all, diff --git a/Tests/ViewInspectorTests/SwiftUI/MenuTests.swift b/Tests/ViewInspectorTests/SwiftUI/MenuTests.swift index 8e5e9f61..c08ec0e8 100644 --- a/Tests/ViewInspectorTests/SwiftUI/MenuTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/MenuTests.swift @@ -2,7 +2,7 @@ import XCTest import SwiftUI @testable import ViewInspector -#if !os(tvOS) +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) final class MenuTests: XCTestCase { diff --git a/Tests/ViewInspectorTests/SwiftUI/NavigationLinkTests.swift b/Tests/ViewInspectorTests/SwiftUI/NavigationLinkTests.swift index 9a12d564..f15d6f55 100644 --- a/Tests/ViewInspectorTests/SwiftUI/NavigationLinkTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/NavigationLinkTests.swift @@ -34,6 +34,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertNoThrow(try view.inspect().anyView().navigationLink()) } + @available(watchOS 7.0, *) func testExtractionFromMultipleViewContainer() throws { let view = NavigationView { NavigationLink( @@ -45,6 +46,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertNoThrow(try view.inspect().navigationView().navigationLink(1)) } + @available(watchOS 7.0, *) func testSearch() throws { let view = AnyView(NavigationView { NavigationLink( @@ -64,6 +66,7 @@ final class NavigationLinkTests: XCTestCase { "anyView().navigationView().navigationLink(1).labelView().text()") } + @available(watchOS 7.0, *) func testNavigationWithoutBindingAndState() throws { guard #available(iOS 13.1, macOS 10.16, tvOS 13.1, *) else { return } let view = NavigationView { @@ -77,6 +80,7 @@ final class NavigationLinkTests: XCTestCase { "Enable programmatic navigation by using `NavigationLink(destination:, tag:, selection:)`") } + @available(watchOS 7.0, *) func testNavigationWithStateActivation() throws { let view = TestViewState() XCTAssertNil(view.state.selection) @@ -92,6 +96,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertFalse(isActiveAfter2) } + @available(watchOS 7.0, *) func testNavigationWithBindingActivation() throws { let selection = Binding(wrappedValue: nil) let view = TestViewBinding(selection: selection) @@ -108,6 +113,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertFalse(isActiveAfter2) } + @available(watchOS 7.0, *) func testNavigationWithStateDeactivation() throws { let view = TestViewState() view.state.selection = view.tag2 @@ -123,6 +129,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertFalse(isActiveAfter2) } + @available(watchOS 7.0, *) func testNavigationWithBindingDeactivation() throws { let selection = Binding(wrappedValue: nil) let view = TestViewBinding(selection: selection) @@ -139,6 +146,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertFalse(isActiveAfter2) } + @available(watchOS 7.0, *) func testNavigationWithStateReactivation() throws { let view = TestViewState() try view.inspect().navigationView().navigationLink(1).activate() @@ -151,6 +159,7 @@ final class NavigationLinkTests: XCTestCase { XCTAssertFalse(isActiveAfter2) } + @available(watchOS 7.0, *) func testNavigationWithBindingReactivation() throws { let selection = Binding(wrappedValue: nil) let view = TestViewBinding(selection: selection) @@ -174,7 +183,7 @@ private struct TestView: View, Inspectable { } } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) private struct TestViewState: View, Inspectable { @ObservedObject var state = NavigationState() @@ -191,7 +200,7 @@ private struct TestViewState: View, Inspectable { } } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) private struct TestViewBinding: View, Inspectable { @Binding var selection: String? @@ -209,7 +218,7 @@ private struct TestViewBinding: View, Inspectable { } } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) extension TestViewState { class NavigationState: ObservableObject { @Published var selection: String? diff --git a/Tests/ViewInspectorTests/SwiftUI/NavigationViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/NavigationViewTests.swift index 51f1c5b1..a66ee565 100644 --- a/Tests/ViewInspectorTests/SwiftUI/NavigationViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/NavigationViewTests.swift @@ -2,7 +2,7 @@ import XCTest import SwiftUI @testable import ViewInspector -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) final class NavigationViewTests: XCTestCase { func testSingleEnclosedView() throws { @@ -56,7 +56,7 @@ final class NavigationViewTests: XCTestCase { // MARK: - View Modifiers -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) final class GlobalModifiersForNavigationView: XCTestCase { func testNavigationViewStyle() throws { diff --git a/Tests/ViewInspectorTests/SwiftUI/OutlineGroupTests.swift b/Tests/ViewInspectorTests/SwiftUI/OutlineGroupTests.swift index 2d3366dd..61408ac6 100644 --- a/Tests/ViewInspectorTests/SwiftUI/OutlineGroupTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/OutlineGroupTests.swift @@ -2,6 +2,7 @@ import XCTest import SwiftUI @testable import ViewInspector +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) final class OutlineGroupTests: XCTestCase { @@ -28,6 +29,7 @@ final class OutlineGroupTests: XCTestCase { ) ] + @available(watchOS, unavailable) func testExtractionFromSingleViewContainer() throws { guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } let view = AnyView(OutlineGroup(values[0], id: \.testValue, children: \.testChildren) { _ in @@ -36,6 +38,7 @@ final class OutlineGroupTests: XCTestCase { XCTAssertNoThrow(try view.inspect().anyView().outlineGroup()) } + @available(watchOS, unavailable) func testExtractionFromMultipleViewContainer() throws { guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } let view = HStack { @@ -48,6 +51,7 @@ final class OutlineGroupTests: XCTestCase { XCTAssertNoThrow(try view.inspect().hStack().outlineGroup(1)) } + @available(watchOS, unavailable) func testSourceDataInspection() throws { guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } let view1 = OutlineGroup(values, id: \.testValue, children: \.testChildren) { _ in @@ -64,6 +68,7 @@ final class OutlineGroupTests: XCTestCase { "Type mismatch: TestTree is not Array>") } + @available(watchOS, unavailable) func testLeafInspection() throws { guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } let view = OutlineGroup(values[0], id: \.testValue, children: \.testChildren) { element in @@ -76,3 +81,4 @@ final class OutlineGroupTests: XCTestCase { XCTAssertEqual(sut, "l2") } } +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/PopoverTests.swift b/Tests/ViewInspectorTests/SwiftUI/PopoverTests.swift index 33a4e152..d886d0d0 100644 --- a/Tests/ViewInspectorTests/SwiftUI/PopoverTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/PopoverTests.swift @@ -2,57 +2,144 @@ import XCTest import SwiftUI @testable import ViewInspector -#if !os(tvOS) +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) final class PopoverTests: XCTestCase { - func testBaseView() throws { + func testPopover() throws { let binding = Binding(wrappedValue: true) - let sut = EmptyView().popover(isPresented: binding, content: { Text("") }) - if #available(iOS 14.2, macOS 11.0, *) { - XCTAssertNoThrow(try sut.inspect().emptyView()) - } else if #available(iOS 14, macOS 10.16, *) { - XCTAssertThrows(try sut.inspect().emptyView(), - "Unwrapping the view under popover is not supported on iOS 14.0 and 14.1") - } else { - XCTAssertNoThrow(try sut.inspect().emptyView()) + let sut = EmptyView().popover(isPresented: binding) { Text("") } + XCTAssertNoThrow(try sut.inspect().emptyView()) + } + + func testInspectionErrorNoModifier() throws { + let sut = EmptyView().offset() + XCTAssertThrows(try sut.inspect().emptyView().popover(), + "EmptyView does not have 'popover' modifier") + } + + func testInspectionErrorCustomModifierRequired() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().popover(isPresented: binding) { Text("") } + print("\(Inspector.print(sut) as AnyObject)") + XCTAssertThrows(try sut.inspect().emptyView().popover(), + """ + Please refer to the Guide for inspecting the Popover: \ + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#popover + """) + } + + func testInspectionErrorPopoverNotPresented() throws { + let binding = Binding(wrappedValue: false) + let sut = EmptyView().popover2(isPresented: binding) { Text("") } + XCTAssertThrows(try sut.inspect().emptyView().popover(), + "View for Popover is absent") + } + + func testInspectionErrorPopoverWithItemNotPresented() throws { + let binding = Binding(wrappedValue: nil) + let sut = EmptyView().popover2(item: binding) { Text("\($0)") } + XCTAssertThrows(try sut.inspect().emptyView().popover(), + "View for Popover is absent") + } + + func testContentInspection() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().popover2(isPresented: binding) { + Text("abc") } + let title = try sut.inspect().emptyView().popover().text() + XCTAssertEqual(try title.string(), "abc") + XCTAssertEqual(title.pathToRoot, "emptyView().popover().text()") } - func testPopoverUnwrapping() throws { - guard #available(iOS 14.2, macOS 11.0, *) else { return } + func testContentInteraction() throws { let binding = Binding(wrappedValue: true) - let sut = EmptyView().popover(isPresented: binding, content: { Text("") }) - XCTAssertNoThrow(try sut.inspect().emptyView().popover()) + let sut = EmptyView().popover2(isPresented: binding) { + Text("abc") + Button("xyz", action: { binding.wrappedValue = false }) + } + let button = try sut.inspect().emptyView().popover().button(1) + try button.tap() + XCTAssertFalse(binding.wrappedValue) + XCTAssertEqual(button.pathToRoot, "emptyView().popover().button(1)") } - func testPopoverContentView() throws { - guard #available(iOS 14.2, macOS 11.0, *) else { return } + func testDismiss() throws { let binding = Binding(wrappedValue: true) - let sut = EmptyView().popover(isPresented: binding, content: { Text("test") }) + let sut = EmptyView().popover2(isPresented: binding, content: { Text("") }) + XCTAssertTrue(binding.wrappedValue) + try sut.inspect().popover().dismiss() + XCTAssertFalse(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().popover(), "View for Popover is absent") + } + + func testDismissForItemVersion() throws { + let binding = Binding(wrappedValue: 6) + let sut = EmptyView().popover2(item: binding) { Text("\($0)") } let popover = try sut.inspect().emptyView().popover() - XCTAssertThrows(try popover.contentView(), - "Please substitute 'Text.self' as the parameter for 'contentView()' inspection call") - let value = try popover.contentView(Text.self).text().string() - XCTAssertEqual(value, "test") + XCTAssertEqual(try popover.text().string(), "6") + XCTAssertEqual(binding.wrappedValue, 6) + try popover.dismiss() + XCTAssertNil(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().popover(), "View for Popover is absent") } - func testSearchBlocker() throws { - guard #available(iOS 14.2, macOS 11.0, *) else { return } + func testMultiplePopoversInspection() throws { + let binding1 = Binding(wrappedValue: true) + let binding2 = Binding(wrappedValue: true) + let binding3 = Binding(wrappedValue: true) + let sut = PopoverFindTestView(popover1: binding1, popover2: binding2, popover3: binding3) + let title1 = try sut.inspect().hStack().emptyView(0).popover().text(0) + XCTAssertEqual(try title1.string(), "title_1") + XCTAssertEqual(title1.pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover().text(0)") + let title2 = try sut.inspect().hStack().emptyView(0).popover(1).text(0) + XCTAssertEqual(try title2.string(), "title_3") + XCTAssertEqual(title2.pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover(1).text(0)") + + XCTAssertEqual(try sut.inspect().find(ViewType.Popover.self).text(0).string(), "title_1") + binding1.wrappedValue = false + XCTAssertEqual(try sut.inspect().find(ViewType.Popover.self).text(0).string(), "title_3") + binding3.wrappedValue = false + XCTAssertThrows(try sut.inspect().find(ViewType.Popover.self), + "Search did not find a match") + } + + func testFindAndPathToRoots() throws { let binding = Binding(wrappedValue: true) - let sut = EmptyView().popover(isPresented: binding, content: { Text("abc") }) - XCTAssertThrows(try sut.inspect().find(text: "abc"), - "Search did not find a match. Possible blockers: popover") + let sut = PopoverFindTestView(popover1: binding, popover2: binding, popover3: binding) + + // 1 + XCTAssertEqual(try sut.inspect().find(text: "title_1").pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover().text(0)") + XCTAssertEqual(try sut.inspect().find(text: "button_1").pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover().button(1).labelView().text()") + // 2 + XCTAssertThrows(try sut.inspect().find(text: "title_2").pathToRoot, + "Search did not find a match") + + // 3 + XCTAssertEqual(try sut.inspect().find(text: "title_3").pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover(1).text(0)") + + XCTAssertThrows(try sut.inspect().find(text: "message_3").pathToRoot, + "Search did not find a match") + XCTAssertEqual(try sut.inspect().find(text: "button_3").pathToRoot, + "view(PopoverFindTestView.self).hStack().emptyView(0).popover(1).button(1).labelView().text()") } func testArrowEdge() throws { guard #available(iOS 14.2, macOS 11.0, *) else { return } let binding = Binding(wrappedValue: true) let sut = EmptyView() - .popover(isPresented: binding, arrowEdge: .top) { Text("") } + .popover2(isPresented: binding, arrowEdge: .trailing) { Text("") } let value = try sut.inspect().emptyView().popover().arrowEdge() - XCTAssertEqual(value, .top) + XCTAssertEqual(value, .trailing) } func testAttachmentAnchor() throws { @@ -60,29 +147,11 @@ final class PopoverTests: XCTestCase { let binding = Binding(wrappedValue: true) let anchor = PopoverAttachmentAnchor.point(.bottom) let sut = EmptyView() - .popover(isPresented: binding, - attachmentAnchor: anchor) { Text("") } + .popover2(isPresented: binding, + attachmentAnchor: anchor) { Text("") } let value = try sut.inspect().emptyView().popover().attachmentAnchor() XCTAssertEqual(value, anchor) } - - func testIsPresentedAndDismiss() throws { - guard #available(iOS 14.2, macOS 11.0, *) else { return } - let binding = Binding(wrappedValue: true) - let sut = EmptyView().popover(isPresented: binding) { Text("") } - let popover = try sut.inspect().emptyView().popover() - XCTAssertTrue(try popover.isPresented()) - try popover.dismiss() - XCTAssertFalse(try popover.isPresented()) - } - - func testPathToRoot() throws { - guard #available(iOS 14.2, macOS 11.0, *) else { return } - let binding = Binding(wrappedValue: true) - let view = EmptyView().popover(isPresented: binding) { Text("") } - let sut = try view.inspect().emptyView().popover().pathToRoot - XCTAssertEqual(sut, "emptyView().popover()") - } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) @@ -102,4 +171,116 @@ final class PopoverAttachmentAnchorTests: XCTestCase { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +final class PopoverDeprecatedTests: XCTestCase { + + @available(*, deprecated) + func testContentView() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().popover2(isPresented: binding) { Text("") } + let popover = try sut.inspect().emptyView().popover() + XCTAssertNoThrow(try popover.contentView().text()) + XCTAssertNoThrow(try popover.contentView(Text.self).text()) + } + + @available(*, deprecated) + func testIsPresentedAndDismiss() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().popover2(isPresented: binding) { Text("") } + XCTAssertTrue(try sut.inspect().emptyView().popover().isPresented()) + try sut.inspect().emptyView().popover().dismiss() + XCTAssertThrows(try sut.inspect().emptyView().popover(), + "View for Popover is absent") + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private extension View { + func popover2(isPresented: Binding, + attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds), + arrowEdge: Edge = .top, + @ViewBuilder content: @escaping () -> Popover + ) -> some View where Popover: View { + return self.modifier(InspectablePopover( + isPresented: isPresented, + attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, + popupBuilder: content)) + } + + func popover2(item: Binding, + attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds), + arrowEdge: Edge = .top, + content: @escaping (Item) -> Popover + ) -> some View where Item: Identifiable, Popover: View { + return self.modifier(InspectablePopoverWithItem( + item: item, + attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, + popupBuilder: content)) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private struct InspectablePopover: ViewModifier, PopupPresenter where Popover: View { + + let isPresented: Binding + let attachmentAnchor: PopoverAttachmentAnchor + let arrowEdge: Edge + let popupBuilder: () -> Popover + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.popover(isPresented: isPresented, attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, content: popupBuilder) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private struct InspectablePopoverWithItem: ViewModifier, ItemPopupPresenter +where Item: Identifiable, Popover: View { + + let item: Binding + let attachmentAnchor: PopoverAttachmentAnchor + let arrowEdge: Edge + let popupBuilder: (Item) -> Popover + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.popover(item: item, attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, content: popupBuilder) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +private struct PopoverFindTestView: View, Inspectable { + + @Binding var isPopover1Presented = false + @Binding var isPopover2Presented = false + @Binding var isPopover3Presented = false + + init(popover1: Binding, popover2: Binding, popover3: Binding) { + _isPopover1Presented = popover1 + _isPopover2Presented = popover2 + _isPopover3Presented = popover3 + } + + var body: some View { + HStack { + EmptyView() + .popover2(isPresented: $isPopover1Presented) { + Text("title_1") + Button("button_1", action: { }) + } + .popover(isPresented: $isPopover2Presented) { + Text("title_2") + } + .popover2(isPresented: $isPopover3Presented) { + Text("title_3") + Button("button_3", action: { }) + } + } + } +} + #endif diff --git a/Tests/ViewInspectorTests/SwiftUI/ProgressViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/ProgressViewTests.swift index 9027573e..35bd3b06 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ProgressViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ProgressViewTests.swift @@ -6,13 +6,13 @@ import SwiftUI final class ProgressViewTests: XCTestCase { func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(ProgressView()) XCTAssertNoThrow(try view.inspect().anyView().progressView()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") ProgressView() @@ -22,7 +22,7 @@ final class ProgressViewTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(ProgressView(value: 0, label: { AnyView(Text("abc")) }, currentValueLabel: { Text("xyz") })) XCTAssertEqual(try view.inspect().find(ViewType.ProgressView.self).pathToRoot, @@ -34,7 +34,7 @@ final class ProgressViewTests: XCTestCase { } func testFractionCompletedInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view1 = ProgressView() let view2 = ProgressView("test", value: 0.35) XCTAssertNil(try view1.inspect().progressView().fractionCompleted()) @@ -42,7 +42,7 @@ final class ProgressViewTests: XCTestCase { } func testProgressInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let progress = Progress(totalUnitCount: 100) progress.completedUnitCount = 10 let view = ProgressView(progress) @@ -52,7 +52,7 @@ final class ProgressViewTests: XCTestCase { } func testLabelViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = ProgressView(value: 0, label: { HStack { Text("abc") } }, currentValueLabel: { EmptyView() }) let sut = try view.inspect().progressView().labelView().hStack(0).text(0).string() @@ -60,7 +60,7 @@ final class ProgressViewTests: XCTestCase { } func testCurrentValueLabelViewInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = ProgressView(value: 0, label: { EmptyView() }, currentValueLabel: { HStack { Text("abc") } }) let sut = try view.inspect().progressView().currentValueLabelView().hStack(0).text(0).string() @@ -68,13 +68,13 @@ final class ProgressViewTests: XCTestCase { } func testProgressViewStyleInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = EmptyView().progressViewStyle(CircularProgressViewStyle()) XCTAssertTrue(try sut.inspect().progressViewStyle() is CircularProgressViewStyle) } func testProgressViewStyleConfiguration() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut1 = ProgressViewStyleConfiguration(fractionCompleted: nil) XCTAssertNil(sut1.fractionCompleted) let sut2 = ProgressViewStyleConfiguration(fractionCompleted: 0.9) @@ -82,7 +82,7 @@ final class ProgressViewTests: XCTestCase { } func testCustomProgressViewStyleInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = TestProgressViewStyle() XCTAssertEqual(try sut.inspect(fractionCompleted: nil) .vStack().styleConfigurationLabel(0).brightness(), 3) diff --git a/Tests/ViewInspectorTests/SwiftUI/SafeAreaInsetTests.swift b/Tests/ViewInspectorTests/SwiftUI/SafeAreaInsetTests.swift new file mode 100644 index 00000000..ae8117a5 --- /dev/null +++ b/Tests/ViewInspectorTests/SwiftUI/SafeAreaInsetTests.swift @@ -0,0 +1,79 @@ +import XCTest +import SwiftUI +@testable import ViewInspector + +#if !os(macOS) && !targetEnvironment(macCatalyst) // requires macOS SDK 12.0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +final class SafeAreaInsetTests: XCTestCase { + + func testInspectionNotBlocked() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().safeAreaInset(edge: .bottom) { Text("") } + XCTAssertNoThrow(try sut.inspect().emptyView()) + } + + func testInspectionErrorNoModifier() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().offset() + XCTAssertThrows(try sut.inspect().emptyView().safeAreaInset(), + "EmptyView does not have 'safeAreaInset' modifier") + } + + func testSimpleUnwrap() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().safeAreaInset(edge: .bottom) { Text("") } + XCTAssertEqual(try sut.inspect().emptyView().safeAreaInset().pathToRoot, + "emptyView().safeAreaInset()") + } + + func testContentUnwrap() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().safeAreaInset(edge: .bottom) { Text("abc") } + let text = try sut.inspect().safeAreaInset().text() + XCTAssertEqual(try text.string(), "abc") + } + + func testEdge() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().safeAreaInset(edge: .bottom) { Text("") } + XCTAssertEqual(try sut.inspect().safeAreaInset().edge(), .bottom) + } + + func testSpacing() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut1 = EmptyView().safeAreaInset(edge: .bottom, spacing: 19) { Text("") } + let sut2 = EmptyView().safeAreaInset(edge: .bottom) { Text("") } + XCTAssertEqual(try sut1.inspect().safeAreaInset().spacing(), 19) + XCTAssertNil(try sut2.inspect().safeAreaInset().spacing()) + } + + func testRegions() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = EmptyView().safeAreaInset(edge: .bottom) { Text("") } + XCTAssertEqual(try sut.inspect().safeAreaInset().regions(), .container) + } + + func testSearch() throws { + guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + else { return } + let sut = Group { + EmptyView() + Text("") + .safeAreaInset(edge: .top) { EmptyView(); Text("1") } + .padding() + .safeAreaInset(edge: .leading) { Text("2") } + } + XCTAssertEqual(try sut.inspect().find(text: "1").pathToRoot, + "group().text(1).safeAreaInset().text(1)") + XCTAssertEqual(try sut.inspect().find(text: "2").pathToRoot, + "group().text(1).safeAreaInset(1).text()") + } +} +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/ScrollViewReaderTests.swift b/Tests/ViewInspectorTests/SwiftUI/ScrollViewReaderTests.swift index c17683bc..af3126a9 100644 --- a/Tests/ViewInspectorTests/SwiftUI/ScrollViewReaderTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/ScrollViewReaderTests.swift @@ -6,13 +6,13 @@ import SwiftUI final class ScrollViewReaderTests: XCTestCase { func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(ScrollViewReader { _ in EmptyView() }) XCTAssertNoThrow(try view.inspect().anyView().scrollViewReader()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = HStack { Text("") ScrollViewReader { _ in EmptyView() } @@ -22,7 +22,7 @@ final class ScrollViewReaderTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = AnyView(ScrollViewReader { _ in Text("abc") }) XCTAssertEqual(try view.inspect().find(ViewType.ScrollViewReader.self).pathToRoot, "anyView().scrollViewReader()") @@ -31,7 +31,7 @@ final class ScrollViewReaderTests: XCTestCase { } func testEnclosedView() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = ScrollViewReader { _ in Text("abc") } let value = try view.inspect().scrollViewReader().text().string() XCTAssertEqual(value, "abc") diff --git a/Tests/ViewInspectorTests/SwiftUI/SheetTests.swift b/Tests/ViewInspectorTests/SwiftUI/SheetTests.swift index 18577663..d850453d 100644 --- a/Tests/ViewInspectorTests/SwiftUI/SheetTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/SheetTests.swift @@ -8,7 +8,6 @@ final class SheetTests: XCTestCase { func testSheet() throws { let binding = Binding(wrappedValue: true) let sut = EmptyView().sheet(isPresented: binding) { Text("") } - print("\(Inspector.print(sut) as AnyObject)") XCTAssertNoThrow(try sut.inspect().emptyView()) } @@ -24,7 +23,7 @@ final class SheetTests: XCTestCase { XCTAssertThrows(try sut.inspect().emptyView().sheet(), """ Please refer to the Guide for inspecting the Sheet: \ - https://github.com/nalexn/ViewInspector/blob/master/guide.md#sheet + https://github.com/nalexn/ViewInspector/blob/master/guide_popups.md#sheet """) } @@ -64,26 +63,28 @@ final class SheetTests: XCTestCase { XCTAssertEqual(button.pathToRoot, "emptyView().sheet().button(1)") } - func testOnDismiss() throws { + func testDismiss() throws { let exp = XCTestExpectation(description: #function) let binding = Binding(wrappedValue: true) let sut = EmptyView().sheet2(isPresented: binding, onDismiss: { exp.fulfill() }, content: { Text("") }) XCTAssertTrue(binding.wrappedValue) - try sut.inspect().sheet().callOnDismiss() + try sut.inspect().sheet().dismiss() XCTAssertFalse(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().sheet(), "View for Sheet is absent") wait(for: [exp], timeout: 0.1) } - func testContentWithItemInspection() throws { + func testDismissForItemVersion() throws { let binding = Binding(wrappedValue: 6) let sut = EmptyView().sheet2(item: binding) { Text("\($0)") } let sheet = try sut.inspect().emptyView().sheet() XCTAssertEqual(try sheet.text().string(), "6") XCTAssertEqual(binding.wrappedValue, 6) - try sheet.callOnDismiss() + try sheet.dismiss() XCTAssertNil(binding.wrappedValue) + XCTAssertThrows(try sut.inspect().sheet(), "View for Sheet is absent") } func testMultipleSheetsInspection() throws { @@ -138,55 +139,39 @@ private extension View { onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Sheet ) -> some View where Sheet: View { - return self.modifier(InspectableSheet(isPresented: isPresented, onDismiss: onDismiss, content: content)) + return self.modifier(InspectableSheet(isPresented: isPresented, onDismiss: onDismiss, popupBuilder: content)) } func sheet2(item: Binding, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> Sheet ) -> some View where Item: Identifiable, Sheet: View { - return self.modifier(InspectableSheetWithItem(item: item, onDismiss: onDismiss, content: content)) + return self.modifier(InspectableSheetWithItem(item: item, onDismiss: onDismiss, popupBuilder: content)) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableSheet: ViewModifier, SheetProvider where Sheet: View { +private struct InspectableSheet: ViewModifier, PopupPresenter where Sheet: View { let isPresented: Binding let onDismiss: (() -> Void)? - let content: () -> Sheet - let sheetBuilder: () -> Any - - init(isPresented: Binding, onDismiss: (() -> Void)?, content: @escaping () -> Sheet) { - self.isPresented = isPresented - self.onDismiss = onDismiss - self.content = content - self.sheetBuilder = { content() as Any } - } + let popupBuilder: () -> Sheet func body(content: Self.Content) -> some View { - content.sheet(isPresented: isPresented, content: self.content) + content.sheet(isPresented: isPresented, onDismiss: onDismiss, content: popupBuilder) } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) -private struct InspectableSheetWithItem: ViewModifier, SheetItemProvider +private struct InspectableSheetWithItem: ViewModifier, ItemPopupPresenter where Item: Identifiable, Sheet: View { let item: Binding let onDismiss: (() -> Void)? - let content: (Item) -> Sheet - let sheetBuilder: (Item) -> Any - - init(item: Binding, onDismiss: (() -> Void)?, content: @escaping (Item) -> Sheet) { - self.item = item - self.onDismiss = onDismiss - self.content = content - self.sheetBuilder = { content($0) as Any } - } + let popupBuilder: (Item) -> Sheet func body(content: Self.Content) -> some View { - content.sheet(item: item, onDismiss: onDismiss, content: self.content) + content.sheet(item: item, onDismiss: onDismiss, content: popupBuilder) } } diff --git a/Tests/ViewInspectorTests/SwiftUI/TabViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/TabViewTests.swift index c5a5a1d1..ed1cdd79 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TabViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TabViewTests.swift @@ -2,7 +2,7 @@ import XCTest import SwiftUI @testable import ViewInspector -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) final class TabViewTests: XCTestCase { func testEnclosedView() throws { @@ -71,11 +71,13 @@ final class GlobalModifiersForTabView: XCTestCase { XCTAssertEqual(sut, tag) } + @available(watchOS 7.0, *) func testTabItem() throws { let sut = EmptyView().tabItem { Text("") } XCTAssertNoThrow(try sut.inspect().emptyView()) } + @available(watchOS 7.0, *) func testTabItemInspection() throws { let string = "abc" let tabItem = try EmptyView().tabItem { Text(string).blur(radius: 3) } @@ -85,12 +87,13 @@ final class GlobalModifiersForTabView: XCTestCase { XCTAssertEqual(try sut.blur().radius, 3) } + @available(watchOS 7.0, *) func testTabItemSearch() throws { let view = EmptyView().tabItem { Text("abc") } XCTAssertNoThrow(try view.inspect().find(text: "abc")) } - #if !os(macOS) && !targetEnvironment(macCatalyst) + #if os(iOS) || os(tvOS) func testTabViewStyleInspection() throws { guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } let style = PageTabViewStyle(indexDisplayMode: .never) @@ -109,12 +112,11 @@ final class GlobalModifiersForTabView: XCTestCase { XCTAssertNotEqual(styles[index], styles[(index + 1) % styles.count]) } } - #endif - @available(macOS, unavailable) func testIndexViewStyleInspection() throws { guard #available(iOS 14, tvOS 14, *) else { return } let sut = EmptyView().indexViewStyle(PageIndexViewStyle()) XCTAssertTrue(try sut.inspect().indexViewStyle() is PageIndexViewStyle) } + #endif } diff --git a/Tests/ViewInspectorTests/SwiftUI/TextAttributesTests.swift b/Tests/ViewInspectorTests/SwiftUI/TextAttributesTests.swift index 43b1c13d..27947b92 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TextAttributesTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TextAttributesTests.swift @@ -37,7 +37,7 @@ final class TextAttributesTests: XCTestCase { } func testFontAttribute() throws { - let system = Font.system(size: 24, weight: .semibold, design: .monospaced) + let system = Font.system(size: 24, weight: .semibold, design: .rounded) let view1 = Text("Test").kerning(2).font(system) let sut1 = try view1.inspect().text().attributes() XCTAssertEqual(try sut1.font(), system) @@ -96,6 +96,9 @@ final class TextAttributesTests: XCTestCase { let sut = try view.inspect().text().attributes() XCTAssertTrue(try sut.isStrikethrough()) XCTAssertEqual(try sut.strikethroughColor(), .black) + if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + XCTAssertEqual(try sut.strikethroughStyle(), .single) + } } func testUnderlineAttribute() throws { @@ -103,6 +106,9 @@ final class TextAttributesTests: XCTestCase { let sut = try view.inspect().text().attributes() XCTAssertTrue(try sut.isUnderline()) XCTAssertEqual(try sut.underlineColor(), .black) + if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + XCTAssertEqual(try sut.underlineStyle(), .single) + } } func testKerningAttribute() throws { @@ -190,7 +196,7 @@ final class FontAttributesTests: XCTestCase { func testSizeAttribute() throws { let sut1 = Font.custom("abc", size: 13) - let sut2 = Font.system(size: 15, weight: .bold, design: .monospaced) + let sut2 = Font.system(size: 15, weight: .bold, design: .rounded) let sut3 = Font.headline XCTAssertEqual(try sut1.size(), 13) XCTAssertFalse(sut1.isFixedSize()) @@ -199,7 +205,7 @@ final class FontAttributesTests: XCTestCase { XCTAssertThrows(try sut3.size(), "Font does not have 'size' attribute") XCTAssertFalse(sut3.isFixedSize()) - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { let sut4 = Font.custom("abc", fixedSize: 16) XCTAssertEqual(try sut4.size(), 16) XCTAssertTrue(sut4.isFixedSize()) @@ -223,10 +229,10 @@ final class FontAttributesTests: XCTestCase { } func testDesignAttribute() throws { - let sut1 = Font.system(size: 14, design: .monospaced) + let sut1 = Font.system(size: 14, design: .rounded) let sut2 = Font.system(size: 13) let sut3 = Font.custom("abc", size: 14) - XCTAssertEqual(try sut1.design(), .monospaced) + XCTAssertEqual(try sut1.design(), .rounded) XCTAssertEqual(try sut2.design(), .`default`) XCTAssertThrows(try sut3.design(), "Font does not have 'design' attribute") } @@ -242,7 +248,7 @@ final class FontAttributesTests: XCTestCase { } else { XCTAssertThrows(try sut3.style(), "Font does not have 'style' attribute") } - if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { let sut4 = Font.custom("abc", size: 14, relativeTo: .caption2) XCTAssertEqual(try sut4.style(), .caption2) } diff --git a/Tests/ViewInspectorTests/SwiftUI/TextEditorTests.swift b/Tests/ViewInspectorTests/SwiftUI/TextEditorTests.swift index f3ba47d3..60644472 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TextEditorTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TextEditorTests.swift @@ -2,19 +2,21 @@ import XCTest import SwiftUI @testable import ViewInspector +#if os(iOS) || os(macOS) @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) final class TextEditorTests: XCTestCase { func testExtractionFromSingleViewContainer() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: "") let view = AnyView(TextEditor(text: binding)) XCTAssertNoThrow(try view.inspect().anyView().textEditor()) } func testExtractionFromMultipleViewContainer() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: "") let view = HStack { Text("Test") @@ -24,7 +26,7 @@ final class TextEditorTests: XCTestCase { } func testSearch() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: "") let view = AnyView(TextEditor(text: binding)) XCTAssertEqual(try view.inspect().find(ViewType.TextEditor.self).pathToRoot, @@ -32,7 +34,7 @@ final class TextEditorTests: XCTestCase { } func testInput() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: "123") let view = TextEditor(text: binding) let sut = try view.inspect().textEditor() @@ -42,7 +44,7 @@ final class TextEditorTests: XCTestCase { } func testSetInputWhenDisabled() throws { - guard #available(iOS 14, tvOS 14, macOS 11.0, *) else { return } + guard #available(iOS 14, tvOS 14, macOS 11.0, watchOS 7.0, *) else { return } let binding = Binding(wrappedValue: "123") let view = TextEditor(text: binding).disabled(true) let sut = try view.inspect().textEditor() @@ -51,3 +53,4 @@ final class TextEditorTests: XCTestCase { XCTAssertEqual(try sut.input(), "123") } } +#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/TextTests.swift b/Tests/ViewInspectorTests/SwiftUI/TextTests.swift index c639591c..1fa3a8a9 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TextTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TextTests.swift @@ -91,7 +91,7 @@ final class TextTests: XCTestCase { } func testObjectInitialization() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let formatter = NumberFormatter() formatter.numberStyle = .decimal @@ -118,7 +118,7 @@ final class TextTests: XCTestCase { } func testReferenceConvertibleInitialization() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let formatter = DateFormatter() formatter.dateFormat = "yyyy-mm-ss" @@ -138,7 +138,7 @@ final class TextTests: XCTestCase { } func testDateStyleInitialization() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let date = Date(timeIntervalSinceReferenceDate: 123) let sut = Text(date, style: .timer) @@ -147,7 +147,7 @@ final class TextTests: XCTestCase { } func testDateIntervalInitialization() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let date1 = Date(timeIntervalSinceReferenceDate: 123) let date2 = Date(timeIntervalSinceReferenceDate: 123456) @@ -161,7 +161,7 @@ final class TextTests: XCTestCase { } func testTextInterpolation() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = Text("abc \(Text("xyz").bold()) \(Text("qwe"))") let value = try sut.inspect().text().string() @@ -169,7 +169,7 @@ final class TextTests: XCTestCase { } func testImageInterpolation() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let sut = Text("abc \(Image("test"))") let value = try sut.inspect().text().string() @@ -189,7 +189,7 @@ final class TextTests: XCTestCase { } func testImageExtraction() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let image1 = Image("abc").antialiased(true) let image2 = Image("def").resizable() diff --git a/Tests/ViewInspectorTests/SwiftUI/ToolbarTests.swift b/Tests/ViewInspectorTests/SwiftUI/ToolbarTests.swift new file mode 100644 index 00000000..054f8788 --- /dev/null +++ b/Tests/ViewInspectorTests/SwiftUI/ToolbarTests.swift @@ -0,0 +1,190 @@ +import XCTest +import SwiftUI +@testable import ViewInspector + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +final class ToolbarTests: XCTestCase { + + func testToolbarItemPlacementEquatable() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + #if os(iOS) + let values: [ToolbarItemPlacement] = [ + .automatic, .principal, .bottomBar, .navigation, + .navigationBarLeading, .navigationBarTrailing, + .primaryAction, .cancellationAction, .confirmationAction, .destructiveAction] + #else + let values: [ToolbarItemPlacement] = [ + .automatic, .primaryAction, .cancellationAction, + .confirmationAction, .destructiveAction] + #endif + values.enumerated().forEach { lhs in + values.enumerated().forEach { rhs in + if lhs.offset == rhs.offset { + XCTAssertEqual(lhs.element, rhs.element) + } else { + XCTAssertNotEqual(lhs.element, rhs.element) + } + } + } + } + + func testDoesNotBlockInspection() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = Group { + EmptyView().offset().toolbar { Text("") }.padding() + } + XCTAssertNoThrow(try sut.inspect().group().emptyView(0).offset()) + } + + func testSimpleExtraction() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView() + .toolbar { ToolbarItem { Text("abc") } } + let text = try sut.inspect().toolbar().item().text().string() + XCTAssertEqual(text, "abc") + } + + func testMultipleToolbarsExtraction() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView() + .toolbar { ToolbarItem { Text("abc1") } } + .padding() + .toolbar { ToolbarItem { Text("abc2") } } + let text1 = try sut.inspect().toolbar().item().text().string() + XCTAssertEqual(text1, "abc1") + let text2 = try sut.inspect().toolbar(1).item().text().string() + XCTAssertEqual(text2, "abc2") + } + + func testMultipleItemsExtraction() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView() + .toolbar { + ToolbarItem { Text("1") } + ToolbarItemGroup { + Text("2") + } + ToolbarItem { Text("3") } + } + let toolbar = try sut.inspect().toolbar() + XCTAssertEqual(try toolbar.item(0).text().string(), "1") + XCTAssertEqual(try toolbar.itemGroup(1).text().string(), "2") + XCTAssertEqual(try toolbar.item(2).text().string(), "3") + } + + func testMultipleChildViewsExtraction() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView() + .toolbar { + ToolbarItem { Text("1"); Text("2") } + ToolbarItemGroup { Text("3"); Text("4") } + } + let item = try sut.inspect().toolbar().item(0) + let itemGroup = try sut.inspect().toolbar().itemGroup(1) + XCTAssertEqual(try item.text(0).string(), "1") + XCTAssertEqual(try item.text(1).string(), "2") + XCTAssertEqual(try itemGroup.text(0).string(), "3") + XCTAssertEqual(try itemGroup.text(1).string(), "4") + XCTAssertThrows(try sut.inspect().toolbar().item(2), + "View for toolbar item at index 2 is absent") + } + + func testImplicitToolbarItemGroup() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView().toolbar { Text("abc") } + let text = try sut.inspect().toolbar().itemGroup().text().string() + XCTAssertEqual(text, "abc") + } + + func testToolbarIdentifier() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView() + .toolbar { Text("") } + .toolbar(id: "abc") { ToolbarItem(id: "") { Text("") } } + XCTAssertNil(try sut.inspect().toolbar(0).identifier()) + XCTAssertEqual(try sut.inspect().toolbar(1).identifier(), "abc") + } + + func testToolbarItemIdentifier() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView().toolbar { + ToolbarItem(id: "abc") { Text("") } + ToolbarItem(id: "xyz") { EmptyView() } + } + XCTAssertEqual(try sut.inspect().toolbar().item(0).identifier(), "abc") + XCTAssertEqual(try sut.inspect().toolbar().item(1).identifier(), "xyz") + XCTAssertThrows(try sut.inspect().toolbar().item(0).id(), + "ToolbarItem does not have 'id' modifier") + } + + func testToolbarItemPlacement() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView().toolbar { + ToolbarItem(placement: .destructiveAction) { EmptyView() } + ToolbarItem(placement: .primaryAction) { EmptyView() } + ToolbarItem { EmptyView() } + } + let toolbar = try sut.inspect().toolbar() + XCTAssertEqual(try toolbar.item(0).placement(), .destructiveAction) + XCTAssertEqual(try toolbar.item(1).placement(), .primaryAction) + XCTAssertEqual(try toolbar.item(2).placement(), .automatic) + } + + func testToolbarItemGroupPlacement() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView().toolbar { + ToolbarItemGroup(placement: .destructiveAction) { EmptyView() } + ToolbarItemGroup(placement: .primaryAction) { EmptyView() } + ToolbarItemGroup { EmptyView() } + } + let toolbar = try sut.inspect().toolbar() + XCTAssertEqual(try toolbar.itemGroup(0).placement(), .destructiveAction) + XCTAssertEqual(try toolbar.itemGroup(1).placement(), .primaryAction) + XCTAssertEqual(try toolbar.itemGroup(2).placement(), .automatic) + } + + func testToolbarItemShowsByDefault() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = EmptyView().toolbar { + ToolbarItem(id: "", showsByDefault: false) { EmptyView() } + ToolbarItem { EmptyView() } + } + let toolbar = try sut.inspect().toolbar() + XCTAssertFalse(try toolbar.item(0).showsByDefault()) + XCTAssertTrue(try toolbar.item(1).showsByDefault()) + } + + func testSearchAndPathToRoot() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } + let sut = Group { + EmptyView() + .toolbar { + ToolbarItem { Text("1"); Text("2") } + ToolbarItemGroup { HStack { Text("3"); Text("4") } } + } + .padding() + .toolbar { + ToolbarItem { Text("5") } + } + } + XCTAssertEqual(try sut.inspect().find(text: "2").pathToRoot, + "group().emptyView(0).toolbar().item(0).text(1)") + XCTAssertEqual(try sut.inspect().find(text: "3").pathToRoot, + "group().emptyView(0).toolbar().itemGroup(1).hStack().text(0)") + XCTAssertEqual(try sut.inspect().find(text: "5").pathToRoot, + "group().emptyView(0).toolbar(1).item(0).text()") + } +} diff --git a/Tests/ViewInspectorTests/SwiftUI/TouchBarTests.swift b/Tests/ViewInspectorTests/SwiftUI/TouchBarTests.swift index 354b566d..f122b832 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TouchBarTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TouchBarTests.swift @@ -94,7 +94,7 @@ extension TouchBarItemPresence: BinaryEquatable { } final class TouchBarTests: XCTestCase { func testNotSupported() throws { let view = try EmptyView().inspect() - XCTAssertThrows(try view.content.touchBar(parent: view), + XCTAssertThrows(try view.content.touchBar(parent: view, index: 0), "Not supported on this platform") } } diff --git a/Tests/ViewInspectorTests/SwiftUI/TreeViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/TreeViewTests.swift index 4e2a55c7..b199938a 100644 --- a/Tests/ViewInspectorTests/SwiftUI/TreeViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/TreeViewTests.swift @@ -2,17 +2,18 @@ import XCTest import SwiftUI @testable import ViewInspector -#if !os(tvOS) - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, *) +@available(tvOS, unavailable) final class TreeViewTests: XCTestCase { + @available(watchOS, deprecated: 7.0) func testEnclosedView() throws { let sut = Text("Test").contextMenu(ContextMenu(menuItems: { Text("Menu") })) let text = try sut.inspect().text().string() XCTAssertEqual(text, "Test") } + @available(watchOS, deprecated: 7.0) func testRetainsModifiers() throws { let view = Text("Test") .padding() @@ -25,13 +26,13 @@ final class TreeViewTests: XCTestCase { // MARK: - View Modifiers -@available(iOS 13.0, macOS 10.15, tvOS 13.0, *) +@available(iOS 13.0, macOS 10.15, *) +@available(tvOS, unavailable) final class GlobalModifiersForTreeView: XCTestCase { + @available(watchOS, deprecated: 7.0) func testContextMenu() throws { let sut = EmptyView().contextMenu(ContextMenu(menuItems: { Text("") })) XCTAssertNoThrow(try sut.inspect().emptyView()) } } - -#endif diff --git a/Tests/ViewInspectorTests/SwiftUI/VStackTests.swift b/Tests/ViewInspectorTests/SwiftUI/VStackTests.swift index 07285d21..84a96bf5 100644 --- a/Tests/ViewInspectorTests/SwiftUI/VStackTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/VStackTests.swift @@ -71,7 +71,7 @@ final class VStackTests: XCTestCase { } func testSpacingInspection() throws { - guard #available(iOS 14, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let view = VStack(spacing: 6) { Text("") } diff --git a/Tests/ViewInspectorTests/ViewHostingTests.swift b/Tests/ViewInspectorTests/ViewHostingTests.swift index 05bfd861..a23717e4 100644 --- a/Tests/ViewInspectorTests/ViewHostingTests.swift +++ b/Tests/ViewInspectorTests/ViewHostingTests.swift @@ -78,7 +78,7 @@ final class ViewHostingTests: XCTestCase { wait(for: [exp], timeout: 0.2) } } -#else +#elseif os(iOS) || os(tvOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) final class ViewHostingTests: XCTestCase { @@ -149,6 +149,39 @@ final class ViewHostingTests: XCTestCase { wait(for: [exp], timeout: 0.2) } } +#elseif os(watchOS) + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +@available(watchOS, deprecated: 7.0) +final class ViewHostingTests: XCTestCase { + + func testWKTestView() throws { + let exp = XCTestExpectation(description: #function) + exp.expectedFulfillmentCount = 2 + var sut = WKTestView.WrapperView(didUpdate: { + exp.fulfill() + }) + sut.didAppear = { view in + ViewHosting.expel() + } + ViewHosting.host(view: sut) + wait(for: [exp], timeout: 0.1) + } + + func testWKViewExtraction() throws { + let exp = XCTestExpectation(description: #function) + let sut = WKTestView(didUpdate: { }) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + sut.inspect { view in + XCTAssertNoThrow(try view.actualView().interfaceObject()) + ViewHosting.expel() + exp.fulfill() + } + } + ViewHosting.host(view: sut) + wait(for: [exp], timeout: 1) + } +} #endif // MARK: - Test Views @@ -191,7 +224,8 @@ extension NSTestView { } } } -#else +#elseif os(iOS) || os(tvOS) + @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private struct UITestView: UIViewRepresentable, Inspectable { @@ -227,6 +261,38 @@ extension UITestView { } } } +#elseif os(watchOS) + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +@available(watchOS, deprecated: 7.0) +private struct WKTestView: WKInterfaceObjectRepresentable, Inspectable { + + var didUpdate: () -> Void + + typealias Context = WKInterfaceObjectRepresentableContext + func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject { + return WKInterfaceMap() + } + + func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) { + didUpdate() + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) +@available(watchOS, deprecated: 7.0) +extension WKTestView { + struct WrapperView: View, Inspectable { + + var didAppear: ((Self) -> Void)? + var didUpdate: () -> Void + + var body: some View { + WKTestView(didUpdate: didUpdate) + .onAppear { self.didAppear?(self) } + } + } +} #endif #if os(macOS) @@ -257,7 +323,7 @@ private struct NSTestVC: NSViewControllerRepresentable, Inspectable { func updateNSViewController(_ nsViewController: TestVC, context: UpdateContext) { } } -#else +#elseif os(iOS) || os(tvOS) @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) private struct UITestVC: UIViewControllerRepresentable, Inspectable { diff --git a/Tests/ViewInspectorTests/ViewModifiers/AccessibilityModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/AccessibilityModifiersTests.swift index a3a80c18..37155a7f 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/AccessibilityModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/AccessibilityModifiersTests.swift @@ -15,9 +15,17 @@ final class ViewAccessibilityTests: XCTestCase { func testAccessibilityLabelInspection() throws { let string = "abc" - let sut = try EmptyView().accessibility(label: Text(string)) + let sut1 = try EmptyView().accessibility(label: Text(string)) .inspect().emptyView().accessibilityLabel().string() - XCTAssertEqual(sut, string) + XCTAssertEqual(sut1, string) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut2 = try EmptyView().accessibilityLabel(Text(string)) + .inspect().emptyView().accessibilityLabel().string() + let sut3 = try EmptyView().accessibilityLabel(string) + .inspect().emptyView().accessibilityLabel().string() + XCTAssertEqual(sut2, string) + XCTAssertEqual(sut3, string) + } } func testAccessibilityValue() throws { @@ -27,9 +35,17 @@ final class ViewAccessibilityTests: XCTestCase { func testAccessibilityValueInspection() throws { let string = "abc" - let sut = try EmptyView().accessibility(value: Text(string)) + let sut1 = try EmptyView().accessibility(value: Text(string)) .inspect().emptyView().accessibilityValue().string() - XCTAssertEqual(sut, string) + XCTAssertEqual(sut1, string) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut2 = try EmptyView().accessibilityValue(Text(string)) + .inspect().emptyView().accessibilityValue().string() + let sut3 = try EmptyView().accessibilityValue(string) + .inspect().emptyView().accessibilityValue().string() + XCTAssertEqual(sut2, string) + XCTAssertEqual(sut3, string) + } } func testAccessibilityHint() throws { @@ -39,9 +55,17 @@ final class ViewAccessibilityTests: XCTestCase { func testAccessibilityHintInspection() throws { let string = "abc" - let sut = try EmptyView().accessibility(hint: Text(string)) + let sut1 = try EmptyView().accessibility(hint: Text(string)) .inspect().emptyView().accessibilityHint().string() - XCTAssertEqual(sut, string) + XCTAssertEqual(sut1, string) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut2 = try EmptyView().accessibilityHint(Text(string)) + .inspect().emptyView().accessibilityHint().string() + let sut3 = try EmptyView().accessibilityHint(string) + .inspect().emptyView().accessibilityHint().string() + XCTAssertEqual(sut2, string) + XCTAssertEqual(sut3, string) + } } func testAccessibilityHidden() throws { @@ -56,6 +80,14 @@ final class ViewAccessibilityTests: XCTestCase { let sut2 = try EmptyView().accessibility(hidden: false) .inspect().emptyView().accessibilityHidden() XCTAssertFalse(sut2) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut3 = try EmptyView().accessibilityHidden(true) + .inspect().emptyView().accessibilityHidden() + let sut4 = try EmptyView().accessibilityHidden(false) + .inspect().emptyView().accessibilityHidden() + XCTAssertTrue(sut3) + XCTAssertFalse(sut4) + } } func testAccessibilityIdentifier() throws { @@ -65,14 +97,20 @@ final class ViewAccessibilityTests: XCTestCase { func testAccessibilityIdentifierInspection() throws { let string = "abc" - let sut = try EmptyView().accessibility(identifier: string) + let sut1 = try EmptyView().accessibility(identifier: string) .inspect().emptyView().accessibilityIdentifier() - XCTAssertEqual(sut, string) + XCTAssertEqual(sut1, string) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut2 = try EmptyView().accessibilityIdentifier(string) + .inspect().emptyView().accessibilityIdentifier() + XCTAssertEqual(sut2, string) + } } @available(iOS, deprecated, introduced: 13.0) @available(tvOS, deprecated, introduced: 13.0) @available(macOS, deprecated, introduced: 10.15) + @available(watchOS, deprecated, introduced: 6) func testAccessibilitySelectionIdentifier() throws { guard #available(iOS 13.2, macOS 10.17, tvOS 13.2, *) else { return } let sut = EmptyView().accessibility(selectionIdentifier: "") @@ -82,8 +120,12 @@ final class ViewAccessibilityTests: XCTestCase { @available(iOS, deprecated, introduced: 13.0) @available(tvOS, deprecated, introduced: 13.0) @available(macOS, deprecated, introduced: 10.15) + @available(watchOS, deprecated, introduced: 6) func testAccessibilitySelectionIdentifierInspection() throws { guard #available(iOS 13.2, macOS 10.17, tvOS 13.2, *) else { return } + if #available(iOS 15, tvOS 15, *) { + throw XCTSkip("Deprecated modifier with no replacement in iOS 15") + } let string = "abc" let sut = try EmptyView().accessibility(selectionIdentifier: string) .inspect().emptyView().accessibilitySelectionIdentifier() @@ -97,9 +139,14 @@ final class ViewAccessibilityTests: XCTestCase { func testAccessibilityActivationPointInspection() throws { let point: UnitPoint = .bottomTrailing - let sut = try EmptyView().accessibility(activationPoint: point) + let sut1 = try EmptyView().accessibility(activationPoint: point) .inspect().emptyView().accessibilityActivationPoint() - XCTAssertEqual(sut, point) + XCTAssertEqual(sut1, point) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + let sut2 = try EmptyView().accessibilityActivationPoint(point) + .inspect().emptyView().accessibilityActivationPoint() + XCTAssertEqual(sut2, point) + } } func testAccessibilityAction() throws { @@ -108,11 +155,17 @@ final class ViewAccessibilityTests: XCTestCase { } func testAccessibilityActionInspection() throws { + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + else { return } let exp = XCTestExpectation(description: "accessibilityAction") + exp.expectedFulfillmentCount = 2 let sut = EmptyView().accessibilityAction(.default) { exp.fulfill() + }.accessibilityAction(named: "custom") { + exp.fulfill() } try sut.inspect().emptyView().callAccessibilityAction(.default) + try sut.inspect().emptyView().callAccessibilityAction("custom") wait(for: [exp], timeout: 0.1) } @@ -121,6 +174,9 @@ final class ViewAccessibilityTests: XCTestCase { XCTAssertThrows( try sut.inspect().emptyView().callAccessibilityAction(.default), "EmptyView does not have 'accessibilityAction(.default)' modifier") + XCTAssertThrows( + try sut.inspect().emptyView().callAccessibilityAction(.init(named: Text("123"))), + "EmptyView does not have 'accessibilityAction(named: \"123\")' modifier") } func testAccessibilityActionInspectionMultipleCallbacks() throws { diff --git a/Tests/ViewInspectorTests/ViewModifiers/EnvironmentModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/EnvironmentModifiersTests.swift index bbb2a5e1..1736f374 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/EnvironmentModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/EnvironmentModifiersTests.swift @@ -14,6 +14,8 @@ final class ViewEnvironmentTests: XCTestCase { XCTAssertNoThrow(try sut.inspect().emptyView().environment(\.testKey)) XCTAssertThrows(try EmptyView().inspect().emptyView().environment(\.testKey), "EmptyView does not have 'environment(TestEnvKey)' modifier") + let sut2 = EmptyView().environment(\.colorScheme, .light) + XCTAssertEqual(try sut2.inspect().emptyView().environment(\.colorScheme), .light) } func testEnvironmentObject() throws { diff --git a/Tests/ViewInspectorTests/ViewModifiers/EventsModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/EventsModifiersTests.swift index 169d71fb..acd32144 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/EventsModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/EventsModifiersTests.swift @@ -38,7 +38,7 @@ final class ViewEventsTests: XCTestCase { } func testOnChange() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let val = "" let sut = EmptyView().onChange(of: val) { value in } @@ -46,7 +46,7 @@ final class ViewEventsTests: XCTestCase { } func testOnChangeInspection() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let val = "initial" let exp = XCTestExpectation(description: #function) @@ -60,7 +60,7 @@ final class ViewEventsTests: XCTestCase { } func testMultipleOnChangeModifiersSameTypeCallFirst() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } var val = "initial" let other = "" @@ -78,7 +78,7 @@ final class ViewEventsTests: XCTestCase { } func testMultipleOnChangeModifiersSameTypeCallByIndex() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } var val = "initial" let other = "" @@ -98,7 +98,7 @@ final class ViewEventsTests: XCTestCase { } func testMultipleOnChangeModifiersDifferentTypes() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let exp1 = XCTestExpectation(description: "onChange1") let exp2 = XCTestExpectation(description: "onChange2") diff --git a/Tests/ViewInspectorTests/ViewModifiers/InteractionModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/InteractionModifiersTests.swift index fa16b337..df173e37 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/InteractionModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/InteractionModifiersTests.swift @@ -40,7 +40,7 @@ final class InteractionTests: XCTestCase { } func testOnPasteCommand() throws { - let sut = EmptyView().onPasteCommand(of: []) { _ in } + let sut = EmptyView().onPasteCommand(of: [String]()) { _ in } XCTAssertNoThrow(try sut.inspect().emptyView()) } @@ -58,7 +58,7 @@ final class InteractionTests: XCTestCase { func testOnPasteCommandPayload() throws { let sut = EmptyView() - .onPasteCommand(of: [], validator: { _ in nil as Void? }, perform: { _ in }) + .onPasteCommand(of: [String](), validator: { _ in nil as Void? }, perform: { _ in }) XCTAssertNoThrow(try sut.inspect().emptyView()) } @@ -131,16 +131,16 @@ final class InteractionTests: XCTestCase { #if os(macOS) func testOnCommand() throws { - let sut = EmptyView().onCommand(#selector(setUp)) { } + let sut = EmptyView().onCommand(#selector(Self.setUp)) { } XCTAssertNoThrow(try sut.inspect().emptyView()) } func testOnCommandInspection() throws { let exp = XCTestExpectation(description: "onCommand") - let sut = EmptyView().onCommand(#selector(setUp)) { + let sut = EmptyView().onCommand(#selector(Self.setUp)) { exp.fulfill() } - try sut.inspect().emptyView().callOnCommand(#selector(setUp)) + try sut.inspect().emptyView().callOnCommand(#selector(Self.setUp)) wait(for: [exp], timeout: 0.1) } #endif @@ -211,13 +211,13 @@ final class ViewDragDropTests: XCTestCase { } func testOnDropDelegate() throws { - let sut = EmptyView().onDrop(of: [], delegate: DummyDropDelegate()) + let sut = EmptyView().onDrop(of: [String](), delegate: DummyDropDelegate()) XCTAssertNoThrow(try sut.inspect().emptyView()) } func testOnDropCallback() throws { let binding = Binding(wrappedValue: true) - let sut = EmptyView().onDrop(of: [], isTargeted: binding, perform: { _ in false }) + let sut = EmptyView().onDrop(of: [String](), isTargeted: binding, perform: { _ in false }) XCTAssertNoThrow(try sut.inspect().emptyView()) } #endif diff --git a/Tests/ViewInspectorTests/ViewModifiers/TextInputModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/TextInputModifiersTests.swift index 3503556b..c343fc35 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/TextInputModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/TextInputModifiersTests.swift @@ -7,12 +7,14 @@ import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) final class TextInputModifiersTests: XCTestCase { - #if !os(macOS) + #if os(iOS) || os(tvOS) || os(watchOS) func testTextContentType() throws { let sut = EmptyView().textContentType(.emailAddress) XCTAssertNoThrow(try sut.inspect().emptyView()) } + #endif + #if (os(iOS) || os(tvOS)) && !targetEnvironment(macCatalyst) func testTextContentTypeInspection() throws { let sut = AnyView(EmptyView()).textContentType(.emailAddress) XCTAssertEqual(try sut.inspect().anyView().textContentType(), .emailAddress) @@ -20,13 +22,20 @@ final class TextInputModifiersTests: XCTestCase { } #endif - #if os(iOS) || os(tvOS) + #if (os(iOS) || os(tvOS)) && !targetEnvironment(macCatalyst) func testKeyboardType() throws { let sut = EmptyView().keyboardType(.namePhonePad) XCTAssertNoThrow(try sut.inspect().emptyView()) } func testKeyboardTypeInspection() throws { + if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) { + throw XCTSkip( + """ + Implementation should be similar to 'autocapitalization', but new \ + 'KeyboardType' is a private type in SwiftUI + """) + } let sut = AnyView(EmptyView()).keyboardType(.namePhonePad) XCTAssertEqual(try sut.inspect().anyView().keyboardType(), .namePhonePad) XCTAssertEqual(try sut.inspect().anyView().emptyView().keyboardType(), .namePhonePad) @@ -136,6 +145,7 @@ final class TextInputModifiersTests: XCTestCase { XCTAssertTrue(try sut.inspect().anyView().emptyView().allowsTightening()) } + #if !os(watchOS) func testDisableAutocorrection() throws { let sut = EmptyView().disableAutocorrection(false) XCTAssertNoThrow(try sut.inspect().emptyView()) @@ -146,6 +156,7 @@ final class TextInputModifiersTests: XCTestCase { XCTAssertEqual(try sut.inspect().anyView().disableAutocorrection(), false) XCTAssertEqual(try sut.inspect().anyView().emptyView().disableAutocorrection(), false) } + #endif func testFlipsForRightToLeftLayoutDirection() throws { let sut = EmptyView().flipsForRightToLeftLayoutDirection(true) diff --git a/Tests/ViewInspectorTests/ViewModifiers/TransitiveModifiersTests.swift b/Tests/ViewInspectorTests/ViewModifiers/TransitiveModifiersTests.swift index fac3d543..ab579602 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/TransitiveModifiersTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/TransitiveModifiersTests.swift @@ -23,9 +23,10 @@ final class TransitiveModifiersTests: XCTestCase { } @available(tvOS, unavailable) + @available(watchOS, unavailable) func testFlipsRightToLeftInheritance() throws { let sut = try FlipsRightToLeftTestView().inspect() - if #available(iOS 14.0, *) { + if #available(iOS 14.0, tvOS 14.0, *) { XCTAssertFalse(try sut.find(text: "1").flipsForRightToLeftLayoutDirection()) } else { // Prior to iOS 14 flipsForRightToLeftLayoutDirection is ignoring the Bool parameter @@ -65,6 +66,7 @@ final class TransitiveModifiersTests: XCTestCase { } @available(tvOS, unavailable) + @available(watchOS, unavailable) func testLabelsHiddenInheritance() throws { let sut = try TestLabelsHiddenView().inspect() let text1 = try sut.find(text: "1") @@ -111,6 +113,7 @@ private struct TestDisabledView: View, Inspectable { @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private struct FlipsRightToLeftTestView: View, Inspectable { var body: some View { VStack { @@ -146,11 +149,11 @@ private struct AllowsHitTestingTestView: View, Inspectable { var body: some View { VStack { - Button("1", action: { print("1") }) + Button("1", action: { }) VStack { - Button("2", action: { print("2") }) + Button("2", action: { }) VStack { - Button("3", action: { print("3") }) + Button("3", action: { }) .allowsHitTesting(true) }.allowsHitTesting(false) }.allowsHitTesting(true) @@ -160,6 +163,7 @@ private struct AllowsHitTestingTestView: View, Inspectable { @available(iOS 13.0, macOS 10.15, *) @available(tvOS, unavailable) +@available(watchOS, unavailable) private struct TestLabelsHiddenView: View, Inspectable { var body: some View { VStack { diff --git a/Tests/ViewInspectorTests/ViewModifiers/ViewPaddingTests.swift b/Tests/ViewInspectorTests/ViewModifiers/ViewPaddingTests.swift index 41cf9239..14654fb5 100644 --- a/Tests/ViewInspectorTests/ViewModifiers/ViewPaddingTests.swift +++ b/Tests/ViewInspectorTests/ViewModifiers/ViewPaddingTests.swift @@ -32,9 +32,24 @@ final class ViewPaddingTests: XCTestCase { } func testPaddingEdgeSetInspection() throws { - let sut = try EmptyView().padding(.horizontal, 5).inspect().emptyView().padding() - // Looks like a bug in SwiftUI. All edges are set: - XCTAssertEqual(sut, EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)) + let sut = EmptyView().padding(.horizontal, 5) + XCTAssertEqual(try sut.inspect().emptyView().padding(), + EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)) + } + + func testDefaultPaddingValueError() throws { + let sut1 = EmptyView().padding() + let sut2 = EmptyView().padding([.leading]) + let sut3 = EmptyView().offset() + XCTAssertThrows(try sut1.inspect().emptyView().padding(), + "Please use `hasPadding(_:)` for inspecting padding without explicit value.") + XCTAssertThrows(try sut2.inspect().emptyView().padding(), + """ + Undefined inset for 'leading' edge. Consider calling `hasPadding(_:)` \ + instead to assure a default padding is applied. + """) + XCTAssertThrows(try sut3.inspect().emptyView().padding(), + "EmptyView does not have 'padding' modifier") } func testHasDefaultPadding() throws { @@ -54,7 +69,8 @@ final class ViewPaddingTests: XCTestCase { XCTAssertFalse(try sut.inspect().hasPadding([.trailing, .bottom])) XCTAssertFalse(try sut.inspect().hasPadding([.trailing, .top])) - XCTAssertFalse(try sut.inspect().hasPadding([.all])) + XCTAssertFalse(try sut.inspect().hasPadding(.all)) + XCTAssertTrue(try sut.inspect().hasPadding([])) } func testHasLeadingAndTrailingPadding() throws { @@ -89,17 +105,46 @@ final class ViewPaddingTests: XCTestCase { XCTAssertEqual(try sut.inspect().padding([.top]), 10) XCTAssertEqual(try sut.inspect().padding([.bottom]), 20) XCTAssertThrows(try sut.inspect().padding([.leading]), - "InspectableView does not have 'padding' modifier") + "Text does not have 'padding' modifier") XCTAssertThrows(try sut.inspect().padding([.trailing]), - "InspectableView does not have 'padding' modifier") + "Text does not have 'padding' modifier") + XCTAssertThrows(try sut.inspect().padding([.top, .bottom]), + """ + Insets for edges '[SwiftUI.Edge.top, SwiftUI.Edge.bottom]' have \ + different values, consider calling `padding` individually per edge. + """) + XCTAssertThrows(try sut.inspect().padding([]), "No edge is specified") } func testHasDifferentPaddingForEdges() throws { let sut = Text("Test").padding([.top, .bottom], 10).padding([.leading, .trailing], 20) - XCTAssertEqual(try sut.inspect().padding([.top]), 10) - XCTAssertEqual(try sut.inspect().padding([.bottom]), 10) - XCTAssertEqual(try sut.inspect().padding([.leading]), 20) - XCTAssertEqual(try sut.inspect().padding([.trailing]), 20) + XCTAssertEqual(try sut.inspect().padding(.top), 10) + XCTAssertEqual(try sut.inspect().padding(.bottom), 10) + XCTAssertEqual(try sut.inspect().padding(.leading), 20) + XCTAssertEqual(try sut.inspect().padding(.trailing), 20) + XCTAssertEqual(try sut.inspect().padding(), .init(top: 10, leading: 20, bottom: 10, trailing: 20)) + } + + func testCumulativePadding() throws { + let sut = Text("Test") + .padding([.top, .bottom], 10) + .padding([.top, .trailing], 20) + .padding(5) + + XCTAssertEqual(try sut.inspect().padding(.top), 35) + XCTAssertEqual(try sut.inspect().padding(), .init(top: 35, leading: 5, bottom: 15, trailing: 25)) + } + + func testCumulativeUndefinedPaddingError() throws { + let sut = Text("Test") + .padding(.trailing) + .padding(.trailing, 10) + XCTAssertThrows(try sut.inspect().padding(.trailing), + """ + Undefined inset for 'trailing' edge. Consider calling `hasPadding(_:)` \ + instead to assure a default padding is applied. + """) + XCTAssertTrue(try sut.inspect().hasPadding(.trailing)) } } diff --git a/Tests/ViewInspectorTests/ViewSearchTests.swift b/Tests/ViewInspectorTests/ViewSearchTests.swift index 5bd1cdcd..789b0117 100644 --- a/Tests/ViewInspectorTests/ViewSearchTests.swift +++ b/Tests/ViewInspectorTests/ViewSearchTests.swift @@ -71,7 +71,7 @@ private struct Test { Text("empty") } } - @available(iOS 14.0, macOS 11.0, tvOS 14.0, *) + @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) struct ConflictingViewTypeNamesStyle: ButtonStyle { public func makeBody(configuration: Configuration) -> some View { Group { @@ -225,7 +225,7 @@ final class ViewSearchTests: XCTestCase { } func testConflictingViewTypeNames() throws { - guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) else { return } + guard #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) else { return } let style = Test.ConflictingViewTypeNamesStyle() let sut = try style.inspect(isPressed: true) XCTAssertEqual(try sut.find(text: "empty").pathToRoot, @@ -235,4 +235,20 @@ final class ViewSearchTests: XCTestCase { XCTAssertEqual(try sut.find(ViewType.StyleConfiguration.Label.self).pathToRoot, "group().styleConfigurationLabel(2)") } + + func testShapesSearching() throws { + let sut = Group { + Circle().inset(by: 5) + Rectangle() + Ellipse().offset(x: 2, y: 3) + } + XCTAssertThrows(try sut.inspect().find(text: "none"), + "Search did not find a match") + let testRect = CGRect(x: 0, y: 0, width: 10, height: 100) + let refPath = Ellipse().offset(x: 2, y: 3).path(in: testRect) + let ellipse = try sut.inspect().find(where: { + try $0.shape().path(in: testRect) == refPath + }) + XCTAssertEqual(ellipse.pathToRoot, "group().shape(2)") + } } diff --git a/ViewInspector.podspec b/ViewInspector.podspec index be5db423..5292d412 100644 --- a/ViewInspector.podspec +++ b/ViewInspector.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = "ViewInspector" - s.version = "0.8.1" + s.version = "0.9.0" s.summary = "ViewInspector is a library for unit testing SwiftUI views." s.homepage = "https://github.com/nalexn/ViewInspector" s.license = { :type => "MIT", :file => "LICENSE" } @@ -10,7 +10,8 @@ Pod::Spec.new do |s| s.ios.deployment_target = '13.0' s.osx.deployment_target = '10.15' - #s.tvos.deployment_target = '13.0' + s.tvos.deployment_target = '13.0' + #s.watchos.deployment_target = '7.0' s.swift_version = '5.0' s.framework = 'XCTest' s.source = { :git => "https://github.com/nalexn/ViewInspector.git", :tag => "#{s.version}" } diff --git a/ViewInspector.xcodeproj/project.pbxproj b/ViewInspector.xcodeproj/project.pbxproj index 1b4f4658..d94d651f 100644 --- a/ViewInspector.xcodeproj/project.pbxproj +++ b/ViewInspector.xcodeproj/project.pbxproj @@ -22,10 +22,15 @@ /* Begin PBXBuildFile section */ 355B2D57BFF536BCD0B555B8 /* ViewPaddingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 355B2B0F773087003E9F5A49 /* ViewPaddingTests.swift */; }; + 520E915C26E64F210090BD1F /* EnvironmentInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520E915B26E64F210090BD1F /* EnvironmentInjection.swift */; }; + 520E916226E69E540090BD1F /* PopupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520E916126E69E540090BD1F /* PopupPresenter.swift */; }; 520F8C13266D0E600026BC57 /* CustomViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520F8C12266D0E600026BC57 /* CustomViewModifier.swift */; }; 5214800325F4EA4F002D974D /* EnvironmentObjectInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5214800225F4EA4F002D974D /* EnvironmentObjectInjectionTests.swift */; }; 5214802B25F803B7002D974D /* CustomStyleModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5214802A25F803B7002D974D /* CustomStyleModifiers.swift */; }; 5214803A25F803DC002D974D /* CustomStyleModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5214803025F803D5002D974D /* CustomStyleModifiersTests.swift */; }; + 5223540026D62CB2008DA52F /* ToolbarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522353FF26D62CB2008DA52F /* ToolbarTests.swift */; }; + 5223540226D63B7A008DA52F /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5223540126D63B79008DA52F /* Toolbar.swift */; }; + 5236C34326CDC2F4007432E1 /* UnaryViewAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5236C34226CDC2F4007432E1 /* UnaryViewAdaptor.swift */; }; 52507626264FC0F400ADE4E7 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52507625264FC0F400ADE4E7 /* Alert.swift */; }; 525076282650000600ADE4E7 /* AlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525076272650000600ADE4E7 /* AlertTests.swift */; }; 52507647265155C000ADE4E7 /* TransitiveModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52507646265155C000ADE4E7 /* TransitiveModifiersTests.swift */; }; @@ -39,7 +44,11 @@ 52A3B51C26591F79001FE17E /* SheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A3B51B26591F79001FE17E /* SheetTests.swift */; }; 52A4A7642621F4AE0063E00B /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A4A7632621F4AE0063E00B /* Overlay.swift */; }; 52A5CA0425E1139E00773CF5 /* EnvironmentModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A5CA0325E1139E00773CF5 /* EnvironmentModifiers.swift */; }; + 52B71AA626EB5BED00B719D4 /* SafeAreaInsetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B71AA526EB5BED00B719D4 /* SafeAreaInsetTests.swift */; }; + 52B71AAA26EB5C4500B719D4 /* SafeAreaInset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B71AA926EB5C4500B719D4 /* SafeAreaInset.swift */; }; 52D37480262B1F0C00B1BFBA /* NestedModifiersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D3747F262B1F0C00B1BFBA /* NestedModifiersTests.swift */; }; + 52E24E9926E9378A00711987 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E24E9826E9378A00711987 /* ConfirmationDialog.swift */; }; + 52E24E9B26E93A4700711987 /* ConfirmationDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E24E9A26E93A4700711987 /* ConfirmationDialogTests.swift */; }; 52F356AA267692D100695E43 /* MapAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F356A9267692D100695E43 /* MapAnnotation.swift */; }; 52F356AC2676940A00695E43 /* MapAnnotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F356AB2676940A00695E43 /* MapAnnotationTests.swift */; }; 79069A6B238E8490000F6B58 /* OptionalContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79069A6A238E8490000F6B58 /* OptionalContent.swift */; }; @@ -69,6 +78,7 @@ CDA8450C262CBF5700C56C98 /* CommonComposedGestureEndedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA8450B262CBF5700C56C98 /* CommonComposedGestureEndedTests.swift */; }; CDA84516262CC1F800C56C98 /* CommonComposedGestureUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA84515262CC1F800C56C98 /* CommonComposedGestureUpdatingTests.swift */; }; CDA8451C262CC34D00C56C98 /* CommonComposedGestureChangedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA8451B262CC34D00C56C98 /* CommonComposedGestureChangedTests.swift */; }; + D766E67026E17F01004AAA80 /* FullScreenCoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D766E66F26E17F01004AAA80 /* FullScreenCoverTests.swift */; }; F6026A27256A7D1900CA31E5 /* TextAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6026A26256A7D1900CA31E5 /* TextAttributes.swift */; }; F6026A31256A7E3D00CA31E5 /* TextAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6026A30256A7E3D00CA31E5 /* TextAttributesTests.swift */; }; F60385D523D3C74B008F31BD /* InspectionEmissary.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60385D423D3C74B008F31BD /* InspectionEmissary.swift */; }; @@ -265,10 +275,15 @@ /* Begin PBXFileReference section */ 355B2B0F773087003E9F5A49 /* ViewPaddingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewPaddingTests.swift; sourceTree = ""; }; + 520E915B26E64F210090BD1F /* EnvironmentInjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentInjection.swift; sourceTree = ""; }; + 520E916126E69E540090BD1F /* PopupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupPresenter.swift; sourceTree = ""; }; 520F8C12266D0E600026BC57 /* CustomViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomViewModifier.swift; sourceTree = ""; }; 5214800225F4EA4F002D974D /* EnvironmentObjectInjectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentObjectInjectionTests.swift; sourceTree = ""; }; 5214802A25F803B7002D974D /* CustomStyleModifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomStyleModifiers.swift; sourceTree = ""; }; 5214803025F803D5002D974D /* CustomStyleModifiersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomStyleModifiersTests.swift; sourceTree = ""; }; + 522353FF26D62CB2008DA52F /* ToolbarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarTests.swift; sourceTree = ""; }; + 5223540126D63B79008DA52F /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; + 5236C34226CDC2F4007432E1 /* UnaryViewAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnaryViewAdaptor.swift; sourceTree = ""; }; 52507625264FC0F400ADE4E7 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; 525076272650000600ADE4E7 /* AlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertTests.swift; sourceTree = ""; }; 52507646265155C000ADE4E7 /* TransitiveModifiersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitiveModifiersTests.swift; sourceTree = ""; }; @@ -280,7 +295,11 @@ 52A3B51B26591F79001FE17E /* SheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetTests.swift; sourceTree = ""; }; 52A4A7632621F4AE0063E00B /* Overlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overlay.swift; sourceTree = ""; }; 52A5CA0325E1139E00773CF5 /* EnvironmentModifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentModifiers.swift; sourceTree = ""; }; + 52B71AA526EB5BED00B719D4 /* SafeAreaInsetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaInsetTests.swift; sourceTree = ""; }; + 52B71AA926EB5C4500B719D4 /* SafeAreaInset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaInset.swift; sourceTree = ""; }; 52D3747F262B1F0C00B1BFBA /* NestedModifiersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedModifiersTests.swift; sourceTree = ""; }; + 52E24E9826E9378A00711987 /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = ""; }; + 52E24E9A26E93A4700711987 /* ConfirmationDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialogTests.swift; sourceTree = ""; }; 52F356A9267692D100695E43 /* MapAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapAnnotation.swift; sourceTree = ""; }; 52F356AB2676940A00695E43 /* MapAnnotationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapAnnotationTests.swift; sourceTree = ""; }; 79069A6A238E8490000F6B58 /* OptionalContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalContent.swift; sourceTree = ""; }; @@ -310,6 +329,7 @@ CDA8450B262CBF5700C56C98 /* CommonComposedGestureEndedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureEndedTests.swift; sourceTree = ""; }; CDA84515262CC1F800C56C98 /* CommonComposedGestureUpdatingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureUpdatingTests.swift; sourceTree = ""; }; CDA8451B262CC34D00C56C98 /* CommonComposedGestureChangedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonComposedGestureChangedTests.swift; sourceTree = ""; }; + D766E66F26E17F01004AAA80 /* FullScreenCoverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCoverTests.swift; sourceTree = ""; }; F6026A26256A7D1900CA31E5 /* TextAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAttributes.swift; sourceTree = ""; }; F6026A30256A7E3D00CA31E5 /* TextAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAttributesTests.swift; sourceTree = ""; }; F60385D423D3C74B008F31BD /* InspectionEmissary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectionEmissary.swift; sourceTree = ""; }; @@ -573,6 +593,8 @@ F64105C3257BF9A70033D82D /* ViewSearch.swift */, F660849A2594AA6700AF59A2 /* ViewSearchIndex.swift */, F6D58B9223C4A0F3000CEE3B /* ViewHosting.swift */, + 520E915B26E64F210090BD1F /* EnvironmentInjection.swift */, + 520E916126E69E540090BD1F /* PopupPresenter.swift */, F68108A423A5833D00B32145 /* Modifiers */, F60EEBBD2382EED0007DB53A /* SwiftUI */, ); @@ -591,6 +613,7 @@ F64C4E2B250CD60800A69FF9 /* Color.swift */, F6FB7F5725476431006658EF /* ColorPicker.swift */, F64057EC238DB1530029D9BA /* ConditionalContent.swift */, + 52E24E9826E9378A00711987 /* ConfirmationDialog.swift */, F60EEBBE2382EED0007DB53A /* CustomView.swift */, 520F8C12266D0E600026BC57 /* CustomViewModifier.swift */, F60EEBBF2382EED0007DB53A /* DatePicker.swift */, @@ -633,6 +656,7 @@ F6C15AA2254DA270000240F1 /* ProgressView.swift */, F6684BFD23AA863400DECCB3 /* RadialGradient.swift */, 52A4A7632621F4AE0063E00B /* Overlay.swift */, + 52B71AA926EB5C4500B719D4 /* SafeAreaInset.swift */, F60EEBC22382EED0007DB53A /* ScrollView.swift */, F6C15AB6254DB173000240F1 /* ScrollViewReader.swift */, F60EEBC42382EED0007DB53A /* Section.swift */, @@ -650,9 +674,11 @@ F6C15AC6254EDA79000240F1 /* TextEditor.swift */, F6D933BE2385F5CB00358E0E /* TextField.swift */, F6D933BA2385F42300358E0E /* Toggle.swift */, + 5223540126D63B79008DA52F /* Toolbar.swift */, F639DBC223A7DDA2003A6FED /* TouchBar.swift */, F64A2C6723A3FD3A00A4853A /* TreeView.swift */, F653BDE2255698A6001FA688 /* TupleView.swift */, + 5236C34226CDC2F4007432E1 /* UnaryViewAdaptor.swift */, F6D933B22385ED1400358E0E /* VSplitView.swift */, F60EEBC92382EED0007DB53A /* VStack.swift */, F60EEBC12382EED0007DB53A /* ZStack.swift */, @@ -697,6 +723,7 @@ F64C4E2F250CD71400A69FF9 /* ColorTests.swift */, F6FB7F6125476480006658EF /* ColorPickerTests.swift */, F6F08E6723A2A67D001F04DF /* ConditionalContentTests.swift */, + 52E24E9A26E93A4700711987 /* ConfirmationDialogTests.swift */, F60EEBEF2382F004007DB53A /* CustomViewTests.swift */, F6B7D0092494E12F00ABB5E0 /* CustomViewBuilderTests.swift */, F69C91CB2383189200515A91 /* CustomViewModifierTests.swift */, @@ -711,6 +738,7 @@ 5214800225F4EA4F002D974D /* EnvironmentObjectInjectionTests.swift */, F60EEBED2382F004007DB53A /* ForEachTests.swift */, F60EEBEB2382F004007DB53A /* FormTests.swift */, + D766E66F26E17F01004AAA80 /* FullScreenCoverTests.swift */, F6D9339C2385ADA500358E0E /* GeometryReaderTests.swift */, F60EEBF12382F004007DB53A /* GroupTests.swift */, F6D933AC2385EB0100358E0E /* GroupBoxTests.swift */, @@ -740,6 +768,7 @@ F6BD82092565AD5D00A772D4 /* PopoverTests.swift */, F6C15A98254D9F24000240F1 /* ProgressViewTests.swift */, F6684BFF23AA864F00DECCB3 /* RadialGradientTests.swift */, + 52B71AA526EB5BED00B719D4 /* SafeAreaInsetTests.swift */, F60EEBF42382F004007DB53A /* ScrollViewTests.swift */, F6C15AAC254DB0AA000240F1 /* ScrollViewReaderTests.swift */, F60EEBF22382F004007DB53A /* SectionTests.swift */, @@ -756,6 +785,7 @@ F6C15AC0254EDA15000240F1 /* TextEditorTests.swift */, F6D933C02385F62500358E0E /* TextFieldTests.swift */, F6D933BC2385F45A00358E0E /* ToggleTests.swift */, + 522353FF26D62CB2008DA52F /* ToolbarTests.swift */, F639DBC423A7DFA6003A6FED /* TouchBarTests.swift */, F64A2C6923A3FE3100A4853A /* TreeViewTests.swift */, F653BDEC25569D2E001FA688 /* TupleViewTests.swift */, @@ -997,6 +1027,8 @@ F6C15ADD254F26B9000240F1 /* StyleConfiguration.swift in Sources */, F639DBC323A7DDA2003A6FED /* TouchBar.swift in Sources */, 52507626264FC0F400ADE4E7 /* Alert.swift in Sources */, + 5236C34326CDC2F4007432E1 /* UnaryViewAdaptor.swift in Sources */, + 5223540226D63B7A008DA52F /* Toolbar.swift in Sources */, F60EEBD32382EED0007DB53A /* ZStack.swift in Sources */, 5214802B25F803B7002D974D /* CustomStyleModifiers.swift in Sources */, F6ECF6CE23A67C64000FC591 /* SizingModifiers.swift in Sources */, @@ -1021,10 +1053,13 @@ F6ECF6C823A66E54000FC591 /* InteractionModifiers.swift in Sources */, 52A5CA0425E1139E00773CF5 /* EnvironmentModifiers.swift in Sources */, F60EEBDC2382EED0007DB53A /* Text.swift in Sources */, + 52B71AAA26EB5C4500B719D4 /* SafeAreaInset.swift in Sources */, F620C7FB2537B090006D856D /* ConfigurationModifiers.swift in Sources */, F6684BFE23AA863400DECCB3 /* RadialGradient.swift in Sources */, + 520E916226E69E540090BD1F /* PopupPresenter.swift in Sources */, F60EEBD42382EED0007DB53A /* ScrollView.swift in Sources */, F64C4E2C250CD60800A69FF9 /* Color.swift in Sources */, + 520E915C26E64F210090BD1F /* EnvironmentInjection.swift in Sources */, F682A016254776F2005F1B70 /* DisclosureGroup.swift in Sources */, F6119FF4254A00FC0000C54A /* LazyHGrid.swift in Sources */, F653BDE3255698A6001FA688 /* TupleView.swift in Sources */, @@ -1042,6 +1077,7 @@ F609EFBD23A40B8800B9256A /* DelayedPreferenceView.swift in Sources */, F6684BF223AA554500DECCB3 /* PasteButton.swift in Sources */, F60EEBD12382EED0007DB53A /* DatePicker.swift in Sources */, + 52E24E9926E9378A00711987 /* ConfirmationDialog.swift in Sources */, F64A2C6823A3FD3A00A4853A /* TreeView.swift in Sources */, F6A35A4025B35E920068B8B2 /* LazyGroup.swift in Sources */, F64057EF238DBA800029D9BA /* EmptyView.swift in Sources */, @@ -1122,6 +1158,7 @@ F614E66625B36ACC000C4F66 /* InspectableViewTests.swift in Sources */, F6684BF823AA58F200DECCB3 /* AngularGradientTests.swift in Sources */, F6684C0023AA864F00DECCB3 /* RadialGradientTests.swift in Sources */, + 52E24E9B26E93A4700711987 /* ConfirmationDialogTests.swift in Sources */, F6C15A31254A0F29000240F1 /* LazyVStackTests.swift in Sources */, CDA844DA262B81CD00C56C98 /* SimultaneousGestureTests.swift in Sources */, F6D933B92385EDDF00358E0E /* PickerTests.swift in Sources */, @@ -1133,6 +1170,7 @@ F639DBC523A7DFA6003A6FED /* TouchBarTests.swift in Sources */, F67540EF23A3D8B30022AC33 /* PositioningModifiersTests.swift in Sources */, F6D933B52385ED2700358E0E /* VSplitViewTests.swift in Sources */, + 52B71AA626EB5BED00B719D4 /* SafeAreaInsetTests.swift in Sources */, F60EEC002382F004007DB53A /* ForEachTests.swift in Sources */, CDA844EC262B84FD00C56C98 /* SimultaneousGestureChildrenTests.swift in Sources */, F64A2C6423A3E74E00A4853A /* PresentationModifiersTests.swift in Sources */, @@ -1191,11 +1229,13 @@ F6A35A4A25B35F710068B8B2 /* LazyGroupTests.swift in Sources */, F60EEBFC2382F004007DB53A /* HStackTests.swift in Sources */, CDA84452262B6A7A00C56C98 /* GestureActionTests.swift in Sources */, + 5223540026D62CB2008DA52F /* ToolbarTests.swift in Sources */, 52F356AC2676940A00695E43 /* MapAnnotationTests.swift in Sources */, F6C15A6B254B669F000240F1 /* MenuTests.swift in Sources */, F64057F1238DBB120029D9BA /* EmptyViewTests.swift in Sources */, CDA844C8262B7F3100C56C98 /* TapGestureTests.swift in Sources */, F6D933B12385EC5F00358E0E /* HSplitViewTests.swift in Sources */, + D766E67026E17F01004AAA80 /* FullScreenCoverTests.swift in Sources */, F64057F3238E9F600029D9BA /* OptionalViewTests.swift in Sources */, F6684BFC23AA842000DECCB3 /* LinearGradientTests.swift in Sources */, F62AEAFC23E705D6003E69D9 /* ViewHostingTests.swift in Sources */, @@ -1283,11 +1323,13 @@ ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,6"; TVOS_DEPLOYMENT_TARGET = 13.0; USE_HEADERMAP = NO; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Debug; }; @@ -1313,7 +1355,6 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; TARGET_NAME = ViewInspector; }; name = Debug; @@ -1340,7 +1381,6 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; TARGET_NAME = ViewInspector; }; name = Release; @@ -1389,11 +1429,13 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,6"; TVOS_DEPLOYMENT_TARGET = 13.0; USE_HEADERMAP = NO; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; @@ -1446,7 +1488,6 @@ PRODUCT_BUNDLE_IDENTIFIER = ViewInspectorTests; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; TARGET_NAME = ViewInspectorTests; }; name = Debug; @@ -1470,7 +1511,6 @@ PRODUCT_BUNDLE_IDENTIFIER = ViewInspectorTests; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; TARGET_NAME = ViewInspectorTests; }; name = Release; diff --git a/ViewInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ViewInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/ViewInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ViewInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ViewInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/ViewInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/guide.md b/guide.md index 4d835060..044d574c 100644 --- a/guide.md +++ b/guide.md @@ -2,14 +2,12 @@ - [The Basics](#the-basics) - [Dynamic query with **find**](#dynamic-query-with-find) -- [Navigation Links](#navigation-links) +- [Inspectable attributes](#inspectable-attributes) - [Views using **@Binding**](#views-using-binding) - [Views using **@ObservedObject**](#views-using-observedobject) - [Views using **@State**, **@Environment** or **@EnvironmentObject**](#views-using-state-environment-or-environmentobject) - [Custom **ViewModifier**](#custom-viewmodifier) -- [Alert, Sheet and ActionSheet](#alert-sheet-and-actionsheet) -- [Styles](guide_styles.md) -- [Gestures](guide_gestures.md) +- [Advanced topics](#advanced-topics) ## The Basics @@ -229,19 +227,28 @@ One of such cases is a custom view that does not conform to `Inspectable`. Addin In addition to that, there are a few SwiftUI modifiers which currently block the search: * `navigationBarItems` -* `popover` * `overlayPreferenceValue` * `backgroundPreferenceValue` While the first two can be unwrapped manually, the last two are notorious for blocking the inspection completely. The workaround is under investigation. -## Navigation Links +## Inspectable attributes + +**ViewInspector** provides access to various parameters held inside Views. + +For a particular view type, there might be available values of `alignment` or `spacing`. For another, there could be `labelView` – a non-standard child view. + +[ViewInspector's API coverage](readiness.md) is the place where you can see all the attributes available for each view type. -A `NavigationLink` contains two views: one for the destination, another for the label. You can examine the label with `labelView()`. +Let's consider `NavigationLink` as an example: it offers `contained view`, `label view`, `isActive: Bool`, `activate()` and `deactivate()`. -The destination is a "contained view" as shown in [ViewInspector's API coverage](readiness.md). Access an inspectable version of the contained view with `view()`, specifying the actual type. From there, you can get the actual view with `actualView()`. +While the last three are self-explanatory, you can see it contains two views: one for the destination, another for the label. -For example, let's say we have a view with a `NavigationLink` inside a `VStack`. The view body looks likes this: +The destination view is the "default" child, which gets returned as you continue chaining the view inspection calls (such as `hStack()` or `view(MyCustomView.self)`) after the `navigationLink()`. Such a direct descendant view is referred to as "contained view". + +Label view, on the other hand, is an additional child view available for inspection on `NavigationLink`. In order to direct ViewInspector to that view, use `labelView()` call after the `navigationLink()`. + +Let's say we have a view with a `NavigationLink` inside a `VStack`. The view body looks likes this: ```swift var body: some View { @@ -269,6 +276,12 @@ let nextView = try link.view(MyView.self).actualView() XCTAssertEqual(nextView.parameter, "Screen 1") ``` +or unwrap the label view and read its contents: + +```swift +let label = try link.labelView().text() +XCTAssertEqual(try label.string(), "Continue") +``` ## Views using `@Binding` @@ -577,227 +590,9 @@ let view = EmptyView().modifier(sut).environmentObject(envObject) ViewHosting.host(view: view) ``` -## Alert, Sheet and ActionSheet - -These three types of views have many in common, so is their inspection mechanism. Due to limited capabilities of what can be achieved in reflection, the native SwiftUI modifiers for presenting these views (`.alert`, `.sheet`, `.actionSheet`) cannot be inspected as-is by the ViewInspector. - -This section discusses how you still can gain the full access to the internals of these views by adding a couple of code snippets to your source code while not making ViewInspector a dependency for the main target. - -### Making `Alert` inspectable - -Add the following snippet to your main target: - -```swift -extension View { - func alert2(isPresented: Binding, content: @escaping () -> Alert) -> some View { - return self.modifier(InspectableAlert(isPresented: isPresented, alertBuilder: content)) - } -} - -struct InspectableAlert: ViewModifier { - - let isPresented: Binding - let alertBuilder: () -> Alert - - func body(content: Self.Content) -> some View { - content.alert(isPresented: isPresented, content: alertBuilder) - } -} -``` - -And tweak the code of your view to use `alert2` instead of `alert`. Feel free to use another name instead of `alert2`. - -Then, add this line in your test target scope: - -```swift -extension InspectableAlert: AlertProvider { } -``` - -After that you'll be able to inspect the `Alert` in the tests: read the `title`, `message`, and access the buttons: - -```swift -func testAlertExample() throws { - let binding = Binding(wrappedValue: true) - let sut = EmptyView().alert2(isPresented: binding) { - Alert(title: Text("Title"), message: Text("Message"), - primaryButton: .destructive(Text("Delete")), - secondaryButton: .cancel(Text("Cancel"))) - } - let alert = try sut.inspect().emptyView().alert() - XCTAssertEqual(try alert.title().string(), "Title") - XCTAssertEqual(try alert.message().string(), "Message") - XCTAssertEqual(try alert.primaryButton().style(), .destructive) - try sut.inspect().find(ViewType.AlertButton.self, containing: "Cancel").tap() -} -``` - -SwiftUI has a second variant of the `Alert` presentation API, which takes a generic `Item` parameter. - -Here is the corresponding snippet for the main target: - -```swift -extension View { - func alert2(item: Binding, content: @escaping (Item) -> Alert) -> some View where Item: Identifiable { - return self.modifier(InspectableAlertWithItem(item: item, alertBuilder: content)) - } -} - -struct InspectableAlertWithItem: ViewModifier { - - let item: Binding - let alertBuilder: (Item) -> Alert - - func body(content: Self.Content) -> some View { - content.alert(item: item, content: alertBuilder) - } -} -``` - -And for the test scope: - -```swift -extension InspectableAlertWithItem: AlertItemProvider { } -``` - -Feel free to add both sets to the project as needed. - -### Making `ActionSheet` inspectable - -Just like with `Alert`, there are two APIs for showing `ActionSheet` in SwiftUI - a simple one taking a `isPresented: Binding` parameter, and a generic version taking `item: Binding` parameter. - -Variant with `isPresented: Binding` - main target snippet: - -```swift -extension View { - func actionSheet2(isPresented: Binding, content: @escaping () -> ActionSheet) -> some View { - return self.modifier(InspectableActionSheet(isPresented: isPresented, sheetBuilder: content)) - } -} - -struct InspectableActionSheet: ViewModifier { - - let isPresented: Binding - let sheetBuilder: () -> ActionSheet - - func body(content: Self.Content) -> some View { - content.actionSheet(isPresented: isPresented, content: sheetBuilder) - } -} -``` - -Test target: - -```swift -extension InspectableActionSheet: ActionSheetProvider { } -``` - -Variant with `item: Binding` - main target snippet: - -```swift -extension View { - func actionSheet2(item: Binding, content: @escaping (Item) -> ActionSheet) -> some View where Item: Identifiable { - return self.modifier(InspectableActionSheetWithItem(item: item, sheetBuilder: content)) - } -} - -struct InspectableActionSheetWithItem: ViewModifier { - - let item: Binding - let sheetBuilder: (Item) -> ActionSheet - - func body(content: Self.Content) -> some View { - content.actionSheet(item: item, content: sheetBuilder) - } -} -``` - -Test target: - -```swift -extension InspectableActionSheetWithItem: ActionSheetItemProvider { } -``` - -Make sure to use `actionSheet2` in your view's body (or a different name of your choice). - -### Making `Sheet` inspectable - -Similarly to the `Alert` and `ActionSheet`, there are two APIs for presenting the `Sheet` thus two sets of snippets to add to the project, depending on your needs. - -Variant with `isPresented: Binding` - main target snippet: - -```swift -extension View { - func sheet2(isPresented: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Sheet - ) -> some View where Sheet: View { - return self.modifier(InspectableSheet(isPresented: isPresented, onDismiss: onDismiss, content: content)) - } -} - -struct InspectableSheet: ViewModifier where Sheet: View { - - let isPresented: Binding - let onDismiss: (() -> Void)? - let content: () -> Sheet - let sheetBuilder: () -> Any - - init(isPresented: Binding, onDismiss: (() -> Void)?, content: @escaping () -> Sheet) { - self.isPresented = isPresented - self.onDismiss = onDismiss - self.content = content - self.sheetBuilder = { content() as Any } - } - - func body(content: Self.Content) -> some View { - content.sheet(isPresented: isPresented, content: self.content) - } -} -``` - -Test target: - -```swift -extension InspectableSheet: SheetProvider { } -``` - -Variant with `item: Binding` - main target snippet: - -```swift -extension View { - func sheet2(item: Binding, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> Sheet - ) -> some View where Item: Identifiable, Sheet: View { - return self.modifier(InspectableSheetWithItem(item: item, onDismiss: onDismiss, content: content)) - } -} - -struct InspectableSheetWithItem: ViewModifier where Item: Identifiable, Sheet: View { - - let item: Binding - let onDismiss: (() -> Void)? - let content: (Item) -> Sheet - let sheetBuilder: (Item) -> Any - - init(item: Binding, onDismiss: (() -> Void)?, content: @escaping (Item) -> Sheet) { - self.item = item - self.onDismiss = onDismiss - self.content = content - self.sheetBuilder = { content($0) as Any } - } - - func body(content: Self.Content) -> some View { - content.sheet(item: item, onDismiss: onDismiss, content: self.content) - } -} -``` - -Test target: - -```swift -extension InspectableSheetWithItem: SheetItemProvider { } -``` - -Don't forget that you'll need to use `sheet2` in place of `sheet` in your views. - ## Advanced topics - [Styles](guide_styles.md) - [Gestures](guide_gestures.md) +- [View Hosting on watchOS](guide_watchOS.md) +- [Alert, Sheet, ActionSheet, FullScreenCover and Popover](guide_popups.md) \ No newline at end of file diff --git a/guide_popups.md b/guide_popups.md new file mode 100644 index 00000000..37ddfcba --- /dev/null +++ b/guide_popups.md @@ -0,0 +1,359 @@ +# System popup views + +- [Alert](#alert) +- [ActionSheet](#actionsheet) +- [Sheet](#sheet) +- [FullScreenCover](#fullscreencover) +- [Popover](#popover) + +These five types of views have many in common, so is their inspection mechanism. Due to limited capabilities of what can be achieved in reflection, the native SwiftUI modifiers for presenting these views (`.alert`, `.actionSheet`, `.sheet`, `.fullScreenCover`, `.popover`) cannot be inspected as-is by the ViewInspector. + +This section discusses how you still can gain the full access to the internals of these views by adding a couple of code snippets to your source code while not making ViewInspector a dependency for the main target. + +*Note*: ViewInspector fully supports `confirmationDialog` inspection without any code tweaking. + +## `Alert` + +If you're using `alert` functions added in iOS 15 (those not marked as deprecated) - you're good to go. + +Otherwise, you'll need to add the following snippet to your main target: + +```swift +extension View { + func alert2(isPresented: Binding, content: @escaping () -> Alert) -> some View { + return self.modifier(InspectableAlert(isPresented: isPresented, popupBuilder: content)) + } +} + +struct InspectableAlert: ViewModifier { + + let isPresented: Binding + let popupBuilder: () -> Alert + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.alert(isPresented: isPresented, content: popupBuilder) + } +} +``` + +And tweak the code of your view to use `alert2` instead of `alert`. Feel free to use another name instead of `alert2`. + +Then, add this line in your test target scope: + +```swift +extension InspectableAlert: PopupPresenter { } +``` + +After that you'll be able to inspect the `Alert` in the tests: read the `title`, `message`, and access the buttons: + +```swift +func testAlertExample() throws { + let binding = Binding(wrappedValue: true) + let sut = EmptyView().alert2(isPresented: binding) { + Alert(title: Text("Title"), message: Text("Message"), + primaryButton: .destructive(Text("Delete")), + secondaryButton: .cancel(Text("Cancel"))) + } + let alert = try sut.inspect().emptyView().alert() + XCTAssertEqual(try alert.title().string(), "Title") + XCTAssertEqual(try alert.message().string(), "Message") + XCTAssertEqual(try alert.primaryButton().style(), .destructive) + try sut.inspect().find(ViewType.AlertButton.self, containing: "Cancel").tap() +} +``` + +SwiftUI has a second variant of the `Alert` presentation API, which takes a generic `Item` parameter. + +Here is the corresponding snippet for the main target: + +```swift +extension View { + func alert2(item: Binding, content: @escaping (Item) -> Alert) -> some View where Item: Identifiable { + return self.modifier(InspectableAlertWithItem(item: item, popupBuilder: content)) + } +} + +struct InspectableAlertWithItem: ViewModifier { + + let item: Binding + let popupBuilder: (Item) -> Alert + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.alert(item: item, content: popupBuilder) + } +} +``` + +And for the test scope: + +```swift +extension InspectableAlertWithItem: ItemPopupPresenter { } +``` + +Feel free to add both sets to the project as needed. + +## `ActionSheet` + +Just like with `Alert`, there are two APIs for showing `ActionSheet` in SwiftUI - a simple one taking a `isPresented: Binding` parameter, and a generic version taking `item: Binding` parameter. + +#### Variant with `isPresented: Binding` - main target snippet: + +```swift +extension View { + func actionSheet2(isPresented: Binding, content: @escaping () -> ActionSheet) -> some View { + return self.modifier(InspectableActionSheet(isPresented: isPresented, popupBuilder: content)) + } +} + +struct InspectableActionSheet: ViewModifier { + + let isPresented: Binding + let popupBuilder: () -> ActionSheet + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.actionSheet(isPresented: isPresented, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableActionSheet: PopupPresenter { } +``` + +#### Variant with `item: Binding` - main target snippet: + +```swift +extension View { + func actionSheet2(item: Binding, content: @escaping (Item) -> ActionSheet) -> some View where Item: Identifiable { + return self.modifier(InspectableActionSheetWithItem(item: item, popupBuilder: content)) + } +} + +struct InspectableActionSheetWithItem: ViewModifier { + + let item: Binding + let popupBuilder: (Item) -> ActionSheet + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.actionSheet(item: item, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableActionSheetWithItem: ItemPopupPresenter { } +``` + +Make sure to use `actionSheet2` in your view's body (or a different name of your choice). + +## `Sheet` + +Similarly to the `Alert` and `ActionSheet`, there are two APIs for presenting the `Sheet` thus two sets of snippets to add to the project, depending on your needs. + +#### Variant with `isPresented: Binding` - main target snippet: + +```swift +extension View { + func sheet2(isPresented: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Sheet + ) -> some View where Sheet: View { + return self.modifier(InspectableSheet(isPresented: isPresented, onDismiss: onDismiss, popupBuilder: content)) + } +} + +struct InspectableSheet: ViewModifier where Sheet: View { + + let isPresented: Binding + let onDismiss: (() -> Void)? + let popupBuilder: () -> Sheet + + func body(content: Self.Content) -> some View { + content.sheet(isPresented: isPresented, onDismiss: onDismiss, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableSheet: PopupPresenter { } +``` + +#### Variant with `item: Binding` - main target snippet: + +```swift +extension View { + func sheet2(item: Binding, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> Sheet + ) -> some View where Item: Identifiable, Sheet: View { + return self.modifier(InspectableSheetWithItem(item: item, onDismiss: onDismiss, popupBuilder: content)) + } +} + +struct InspectableSheetWithItem: ViewModifier where Item: Identifiable, Sheet: View { + + let item: Binding + let onDismiss: (() -> Void)? + let popupBuilder: (Item) -> Sheet + + func body(content: Self.Content) -> some View { + content.sheet(item: item, onDismiss: onDismiss, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableSheetWithItem: ItemPopupPresenter { } +``` + +Don't forget that you'll need to use `sheet2` in place of `sheet` in your views. + +## `FullScreenCover` + +Similarly to the `Alert` and `Sheet`, there are two APIs for presenting the `FullScreenCover` thus two sets of snippets to add to the project, depending on your needs. + +#### Variant with `isPresented: Binding` - main target snippet: + +```swift +extension View { + func fullScreenCover2(isPresented: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> FullScreenCover + ) -> some View where FullScreenCover: View { + return self.modifier(InspectableFullScreenCover(isPresented: isPresented, onDismiss: onDismiss, popupBuilder: content)) + } +} + +struct InspectableFullScreenCover: ViewModifier where FullScreenCover: View { + + let isPresented: Binding + let onDismiss: (() -> Void)? + let popupBuilder: () -> FullScreenCover + + func body(content: Self.Content) -> some View { + content.fullScreenCover(isPresented: isPresented, onDismiss: onDismiss, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableFullScreenCover: PopupPresenter { } +``` + +#### Variant with `item: Binding` - main target snippet: + +```swift +extension View { + func fullScreenCover2(item: Binding, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> FullScreenCover + ) -> some View where Item: Identifiable, FullScreenCover: View { + return self.modifier(InspectableFullScreenCoverWithItem(item: item, onDismiss: onDismiss, popupBuilder: content)) + } +} + +struct InspectableFullScreenCoverWithItem: ViewModifier where Item: Identifiable, FullScreenCover: View { + + let item: Binding + let onDismiss: (() -> Void)? + let popupBuilder: (Item) -> FullScreenCover + + func body(content: Self.Content) -> some View { + content.fullScreenCover(item: item, onDismiss: onDismiss, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectableFullScreenCoverWithItem: ItemPopupPresenter { } +``` + +Don't forget that you'll need to use `fullScreenCover2` in place of `fullScreenCover` in your views. + +## `Popover` + +#### Variant with `isPresented: Binding` - main target snippet: + +```swift +extension View { + func popover2(isPresented: Binding, + attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds), + arrowEdge: Edge = .top, + @ViewBuilder content: @escaping () -> Popover + ) -> some View where Popover: View { + return self.modifier(InspectablePopover( + isPresented: isPresented, + attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, + popupBuilder: content)) + } +} + +struct InspectablePopover: ViewModifier where Popover: View { + + let isPresented: Binding + let attachmentAnchor: PopoverAttachmentAnchor + let arrowEdge: Edge + let popupBuilder: () -> Popover + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.popover(isPresented: isPresented, attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectablePopover: PopupPresenter { } +``` + +#### Variant with `item: Binding` - main target snippet: + +```swift +extension View { + func popover2(item: Binding, + attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds), + arrowEdge: Edge = .top, + content: @escaping (Item) -> Popover + ) -> some View where Item: Identifiable, Popover: View { + return self.modifier(InspectablePopoverWithItem( + item: item, + attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, + popupBuilder: content)) + } +} + +struct InspectablePopoverWithItem: ViewModifier where Item: Identifiable, Popover: View { + + let item: Binding + let attachmentAnchor: PopoverAttachmentAnchor + let arrowEdge: Edge + let popupBuilder: (Item) -> Popover + let onDismiss: (() -> Void)? = nil + + func body(content: Self.Content) -> some View { + content.popover(item: item, attachmentAnchor: attachmentAnchor, + arrowEdge: arrowEdge, content: popupBuilder) + } +} +``` + +Test target: + +```swift +extension InspectablePopoverWithItem: ItemPopupPresenter { } +``` + +Don't forget that you'll need to use `popover2` in place of `popover` in your views. \ No newline at end of file diff --git a/guide_watchOS.md b/guide_watchOS.md new file mode 100644 index 00000000..6d1b9007 --- /dev/null +++ b/guide_watchOS.md @@ -0,0 +1,78 @@ +# View Hosting on watchOS + +Because of WatchKit API limitations **ViewInspector** currently cannot automatically host your views for asynchronous tests - you need to add [this swift file](https://github.com/nalexn/ViewInspector/blob/0.8.2/.watchOS/watchOS-Ext/watchOSApp%2BTestable.swift) to your **watchOS extension target**. + +Then, add the appropriate code snippet, depending on your setup: + +### If your watchOS project is using `@main` + +1. Make sure to add `WKExtensionDelegate` and reference it in the `App` using `@WKExtensionDelegateAdaptor`. +2. Add `let testViewSubject = TestViewSubject([])` as an instance variable to the `ExtensionDelegate`. +3. Add `.testable(extDelegate.testViewSubject)` to your `ContentView` inside `WindowGroup`. + +The final code should look similar to this: + +```swift +final class ExtensionDelegate: NSObject, WKExtensionDelegate { + let testViewSubject = TestViewSubject([]) // #2 +} + +@main +struct MyWatchOSApp: App { + + @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var extDelegate // #1 + + var body: some Scene { + WindowGroup { + ContentView() + .testable(extDelegate.testViewSubject) // #3 + } + } +} +``` + +### If your watchOS project is using `storyboard` + +1. Make sure your root interface controller is a `WKHostingController` descendant. +2. Add `let testViewSubject = TestViewSubject([])` as an instance variable to your root interface controller. +3. Add `.testable(testViewSubject)` to your `ContentView` +4. Replace `var body: ContentView` with `var body: RootView` +5. Replace `WKHostingController` with `WKHostingController>` + +The final code should look similar to this: + +```swift +final class RootInterfaceController: WKHostingController> { // #1 , #5 + + let testViewSubject = TestViewSubject([]) // #2 + + override var body: RootView { // #4 + return ContentView() + .testable(testViewSubject) // #3 + } +} + +struct ContentView: View { + var body: some View { + Text("Hi") + } +} +``` + +## Consequences of intruding the main build target's code + +The proposed code snippets are using `conditional compilation` to minimize the footprint in the main build target. + +The condition `#if !(os(watchOS) && DEBUG)` fully disintegrates the test code in `Release` builds and replaces it with these primitives: + +```swift +typealias RootView = T +typealias TestViewSubject = Set + +extension View { + @inline(__always) + func testable(_ injector: TestViewSubject) -> Self { + self + } +} +``` \ No newline at end of file diff --git a/readiness.md b/readiness.md index af4cfc83..2a7c7009 100644 --- a/readiness.md +++ b/readiness.md @@ -17,21 +17,24 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) | Status | View | Inspectable Attributes | |:---:|---|---| +|:white_check_mark:| ActionSheet | `title view`, `message view`, `button(_ index: Int)`, `dismiss()` | +|:white_check_mark:| Alert | `title view`, `message view`, `actions view`, `primaryButton`, `secondaryButton`, `dismiss()` | |:white_check_mark:| AngularGradient | `gradient: Gradient`, `center: UnitPoint`, `startAngle: Angle`, `endAngle: Angle` | |:white_check_mark:| AnyView | `contained view` | -|:white_check_mark:| Button | `label view`, `tap()` | +|:white_check_mark:| Button | `label view`, `role: ButtonRole?`, `tap()` | |:white_check_mark:| ButtonStyleConfiguration.Label | | |:technologist:| CameraView | | |:white_check_mark:| Color | `value: Color`, `rgba: (Float, Float, Float, Float)`, `name: String` | |:white_check_mark:| ColorPicker | `label view`, `select(color: Color)` | |:white_check_mark:| ConditionalContent | `contained view` | +|:white_check_mark:| ConfirmationDialog | `title view`, `message view`, `actions view`, `titleVisibility: Visibility`, `dismiss()` | |:white_check_mark:| Custom View | `actualView: CustomView`, `viewBuilder container` | |:white_check_mark:| Custom ViewModifier | | |:white_check_mark:| Custom ViewModifier.Content | | |:white_check_mark:| UIViewRepresentable | `uiView: UIView` | |:white_check_mark:| UIViewControllerRepresentable | `viewController: UIViewController` | |:white_check_mark:| DatePicker | `label view`, `select(date: Date)` | -|:white_check_mark:| DisclosureGroup | `label view`, `content view`, `isExpanded: Bool`, `expand()`, `collapse()` | +|:white_check_mark:| DisclosureGroup | `contained view`, `label view`, `isExpanded: Bool`, `expand()`, `collapse()` | |:white_check_mark:| Divider | | |:white_check_mark:| EditButton | `editMode: Binding?` | |:white_check_mark:| EmptyView | | @@ -39,6 +42,7 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:white_check_mark:| Font (*) | `size: CGFloat`, `isFixedSize: Bool`, `name: String`, `weight: Font.Weight`, `design: Font.Design`, `style: Font.TextStyle` | |:white_check_mark:| ForEach | `contained view`, `callOnDelete`, `callOnMove`, `callOnInsert` | |:white_check_mark:| Form | `contained view` | +|:white_check_mark:| FullScreenCover | `dismiss()` | |:white_check_mark:| GeometryReader | `contained view` | |:white_check_mark:| Group | `contained view` | |:white_check_mark:| GroupBox | `contained view`, `label view` | @@ -69,18 +73,20 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:white_check_mark:| OutlineGroup | `leaf view`, `source data` | |:white_check_mark:| PasteButton | `supportedTypes: [String]`| |:white_check_mark:| Picker | `contained view`, `label view`, `select(value: Hashable)` | -|:white_check_mark:| Popover | `content view`, `attachmentAnchor: PopoverAttachmentAnchor`, `arrowEdge: Edge`, `isPresented: Bool`, `dismiss()` | +|:white_check_mark:| Popover | `contained view`, `attachmentAnchor: PopoverAttachmentAnchor`, `arrowEdge: Edge`, `dismiss()` | |:white_check_mark:| PrimitiveButtonStyleConfiguration.Label | | |:white_check_mark:| ProgressView | `label view`, `currentValueLabel view`, `fractionCompleted: Double?`, `progress: Progress` | |:white_check_mark:| ProgressViewStyleConfiguration.CurrentValueLabel | | |:white_check_mark:| ProgressViewStyleConfiguration.Label | | |:white_check_mark:| RadialGradient | `gradient: Gradient`, `center: UnitPoint`, `startRadius: CGFloat`, `endRadius: CGFloat` | +|:white_check_mark:| SafeAreaInset | `regions: SafeAreaRegions`, `spacing: CGFloat?`, `edge: Edge` | |:technologist:| SceneView | | |:white_check_mark:| ScrollView | `contained view`, `contentInsets: EdgeInsets` | |:white_check_mark:| ScrollViewReader | `contained view` | |:white_check_mark:| Section | `contained view`, `header view`, `footer view` | |:white_check_mark:| SecureField | `label view`, `callOnCommit()`, `input: String`, `setInput(_: String)` | |:white_check_mark:| Shape | `func path(in rect: CGRect) -> Path`, `inset: CGFloat`, `offset: CGSize`, `scale: (x: CGFloat, y: CGFloat, anchor: UnitPoint)`, `rotation: (angle: Angle, anchor: UnitPoint)`, `transform: CGAffineTransform`, `size: CGSize`, `strokeStyle: StrokeStyle`, `trim: (from: CGFloat, to: CGFloat)`, `fillShapeStyle() -> ShapeStyle`, `fillStyle: FillStyle` | +|:white_check_mark:| Sheet | `dismiss()` | |:technologist:| SignInWithAppleButton | | |:white_check_mark:| Slider | `label view`, `callOnEditingChanged()`, `value: Double`, `setValue(_: Double)` | |:white_check_mark:| Spacer | `minLength: CGFloat?` | @@ -93,6 +99,7 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:white_check_mark:| TextField | `label view`, `callOnEditingChanged()`, `callOnCommit()`, `input: String`, `setInput(_: String)` | |:white_check_mark:| Toggle | `label view`, `tap()`, `isOn: Bool` | |:white_check_mark:| ToggleStyleConfiguration.Label | | +|:technologist:| ToolbarItem | | |:white_check_mark:| TouchBar | `contained view`, `touchBarID: String` | |:white_check_mark:| TupleView | | |:white_check_mark:| VSplitView | `contained view` | @@ -122,18 +129,19 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:technologist:| `@UIApplicationDelegateAdaptor` | ## Gestures - | Status | Modifier | - |:---:|---| - |:white_check_mark:|`AnyGesture`| - |:white_check_mark:|`DragGesture`| - |:white_check_mark:|`ExclusiveGesture`| - |:white_check_mark:|`GestureStateGesture`| - |:white_check_mark:|`LongPressGesture`| - |:white_check_mark:|`MagnificationGesture`| - |:white_check_mark:|`RotationGesture`| - |:white_check_mark:|`SequenceGesture`| - |:white_check_mark:|`SimultaneousGesture`| - |:white_check_mark:|`TapGesture`| + +| Status | Modifier | +|:---:|---| +|:white_check_mark:| `AnyGesture` | +|:white_check_mark:| `DragGesture` | +|:white_check_mark:| `ExclusiveGesture` | +|:white_check_mark:| `GestureStateGesture` | +|:white_check_mark:| `LongPressGesture` | +|:white_check_mark:| `MagnificationGesture` | +|:white_check_mark:| `RotationGesture` | +|:white_check_mark:| `SequenceGesture` | +|:white_check_mark:| `SimultaneousGesture` | +|:white_check_mark:| `TapGesture` | ## View Modifiers @@ -348,35 +356,21 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:white_check_mark:| `func allowsHitTesting(Bool) -> some View` | |:white_check_mark:| `func contentShape(S, eoFill: Bool) -> some View` | -### Presenting Action Sheets +### Presenting system popup views | Status | Modifier | |:---:|---| |:white_check_mark:| `func actionSheet(isPresented: Binding, content: () -> ActionSheet) -> some View` | |:white_check_mark:| `func actionSheet(item: Binding, content: (T) -> ActionSheet) -> some View` | - -### Presenting Sheets - -| Status | Modifier | -|:---:|---| |:white_check_mark:| `func sheet(isPresented: Binding, onDismiss: (() -> Void)?, content: () -> Content) -> some View` | |:white_check_mark:| `func sheet(item: Binding, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View` | -|:technologist:| `func fullScreenCover(isPresented: Binding, onDismiss: (() -> Void)?, content: () -> Content) -> some View` | -|:technologist:| `func fullScreenCover(item: Binding, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View` | - -### Presenting Alerts - -| Status | Modifier | -|:---:|---| +|: white_check_mark:| `func fullScreenCover(isPresented: Binding, onDismiss: (() -> Void)?, content: () -> Content) -> some View` | +|: white_check_mark:| `func fullScreenCover(item: Binding, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View` | |:white_check_mark:| `func alert(isPresented: Binding, content: () -> Alert) -> some View` | |:white_check_mark:| `func alert(item: Binding, content: (Item) -> Alert) -> some View` | - -### Presenting Popovers - -| Status | Modifier | -|:---:|---| |:white_check_mark:| `func popover(isPresented: Binding, attachmentAnchor: PopoverAttachmentAnchor, arrowEdge: Edge, content: () -> Content) -> some View` | |:white_check_mark:| `func popover(item: Binding, attachmentAnchor: PopoverAttachmentAnchor, arrowEdge: Edge, content: (Item) -> Content) -> some View` | +|:white_check_mark:| `func confirmationDialog(_ title: S, isPresented: Binding, titleVisibility: Visibility, @ViewBuilder actions: () -> A, @ViewBuilder message: () -> M) -> some View` | ### APIs from other Frameworks @@ -557,9 +551,9 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) | Status | Modifier | |:---:|---| -|:technologist:| `func toolbar(content: () -> Content) -> some View` | -|:technologist:| `func toolbar(content: () -> Content) -> some View` | -|:technologist:| `func toolbar(id: String, content: () -> Content) -> some View` | +|: white_check_mark:| `func toolbar(content: () -> Content) -> some View` | +|: white_check_mark:| `func toolbar(content: () -> Content) -> some View` | +|: white_check_mark:| `func toolbar(id: String, content: () -> Content) -> some View` | ### Configuring Context Menu Views @@ -589,28 +583,28 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) | Status | Modifier | |:---:|---| -|:technologist:| `func accessibilityLabel(S) -> ModifiedContent` | -|:technologist:| `func accessibilityLabel(Text) -> ModifiedContent` | -|:technologist:| `func accessibilityLabel(LocalizedStringKey) -> ModifiedContent` | -|:technologist:| `func accessibilityValue(S) -> ModifiedContent` | -|:technologist:| `func accessibilityValue(LocalizedStringKey) -> ModifiedContent` | -|:technologist:| `func accessibilityValue(Text) -> ModifiedContent` | -|:technologist:| `func accessibilityHidden(Bool) -> ModifiedContent` | -|:technologist:| `func accessibilityIdentifier(String) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityLabel(S) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityLabel(Text) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityLabel(LocalizedStringKey) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityValue(S) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityValue(LocalizedStringKey) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityValue(Text) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityHidden(Bool) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityIdentifier(String) -> ModifiedContent` | ### Customizing Accessibility Interactions of a View | Status | Modifier | |:---:|---| -|:technologist:| `func accessibilityHint(LocalizedStringKey) -> ModifiedContent` | -|:technologist:| `func accessibilityHint(Text) -> ModifiedContent` | -|:technologist:| `func accessibilityHint(S) -> ModifiedContent` | -|:technologist:| `func accessibilityActivationPoint(CGPoint) -> ModifiedContent` | -|:technologist:| `func accessibilityActivationPoint(UnitPoint) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityHint(LocalizedStringKey) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityHint(Text) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityHint(S) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityActivationPoint(CGPoint) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityActivationPoint(UnitPoint) -> ModifiedContent` | |:white_check_mark:| `func accessibilityAction(AccessibilityActionKind, () -> Void) -> ModifiedContent` | -|:heavy_check_mark:| `func accessibilityAction(named: Text, () -> Void) -> ModifiedContent` | -|:technologist:| `func accessibilityAction(named: S, () -> Void) -> ModifiedContent` | -|:technologist:| `func accessibilityAction(named: LocalizedStringKey, () -> Void) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityAction(named: Text, () -> Void) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityAction(named: S, () -> Void) -> ModifiedContent` | +|:white_check_mark:| `func accessibilityAction(named: LocalizedStringKey, () -> Void) -> ModifiedContent` | |:white_check_mark:| `func accessibilityAdjustableAction((AccessibilityAdjustmentDirection) -> Void) -> ModifiedContent` | |:white_check_mark:| `func accessibilityScrollAction((Edge) -> Void) -> ModifiedContent` | |:technologist:| `func accessibilityIgnoresInvertColors(Bool) -> some View` | @@ -626,7 +620,7 @@ Visit [this discussion](https://github.com/nalexn/ViewInspector/discussions/60) |:technologist:| `func accessibilityInputLabels([Text]) -> ModifiedContent` | |:technologist:| `func accessibilityAddTraits(AccessibilityTraits) -> ModifiedContent` | |:technologist:| `func accessibilityRemoveTraits(AccessibilityTraits) -> ModifiedContent` | -|:technologist:| `func accessibilitySortPriority(Double) -> ModifiedContent` |` | +|:white_check_mark:| `func accessibilitySortPriority(Double) -> ModifiedContent` |` | |:technologist:| `func accessibilityLinkedGroup(id: ID, in: Namespace.ID) -> some View` | ### Customizing the Help Text of a View