diff --git a/Example/AccessibilitySnapshot.xcodeproj/project.pbxproj b/Example/AccessibilitySnapshot.xcodeproj/project.pbxproj index bb2179bc..eed60ddf 100644 --- a/Example/AccessibilitySnapshot.xcodeproj/project.pbxproj +++ b/Example/AccessibilitySnapshot.xcodeproj/project.pbxproj @@ -45,6 +45,9 @@ 3DF46500220D5FB00048D446 /* ElementSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF464FF220D5FB00048D446 /* ElementSelectionTests.swift */; }; 3DF46502220D7F9B0048D446 /* ElementOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF46501220D7F9B0048D446 /* ElementOrderViewController.swift */; }; 3DF46504220D8C500048D446 /* ElementOrderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF46503220D8C500048D446 /* ElementOrderTests.swift */; }; + 3F8E5DB92535B25000276B32 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8E5DB82535B25000276B32 /* SwiftUIView.swift */; }; + 3FEF854A2538455D0072611F /* SwiftUIView+EmbedInHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEF85492538455D0072611F /* SwiftUIView+EmbedInHostingController.swift */; }; + 3FEF854F253846420072611F /* SwiftUIViewWithScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEF854E253846420072611F /* SwiftUIViewWithScrollView.swift */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* RootViewController.swift */; }; 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; @@ -119,6 +122,9 @@ 3DF464FF220D5FB00048D446 /* ElementSelectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementSelectionTests.swift; sourceTree = ""; }; 3DF46501220D7F9B0048D446 /* ElementOrderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementOrderViewController.swift; sourceTree = ""; }; 3DF46503220D8C500048D446 /* ElementOrderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementOrderTests.swift; sourceTree = ""; }; + 3F8E5DB82535B25000276B32 /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = ""; usesTabs = 0; }; + 3FEF85492538455D0072611F /* SwiftUIView+EmbedInHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftUIView+EmbedInHostingController.swift"; sourceTree = ""; }; + 3FEF854E253846420072611F /* SwiftUIViewWithScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewWithScrollView.swift; sourceTree = ""; }; 4BB19B0B12930973E91DD3A1 /* Pods-AccessibilitySnapshotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AccessibilitySnapshotTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AccessibilitySnapshotTests/Pods-AccessibilitySnapshotTests.debug.xcconfig"; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* AccessibilitySnapshotDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AccessibilitySnapshotDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -172,6 +178,8 @@ isa = PBXGroup; children = ( 3D4674672116A3F100278B57 /* ViewAccessibilityPropertiesViewController.swift */, + 3F8E5DB82535B25000276B32 /* SwiftUIView.swift */, + 3FEF854E253846420072611F /* SwiftUIViewWithScrollView.swift */, 3D39BFAB223314C1009C3EF4 /* TabBarViewController.swift */, 3D04B6D821155942006218A4 /* LabelAccessibilityPropertiesViewController.swift */, 3D04B6DA21155D92006218A4 /* ButtonAccessibilityTraitsViewController.swift */, @@ -248,7 +256,10 @@ 5A17F7876534A258E4588283 /* Pods */, 63E74EA01571917A22FCFF95 /* Frameworks */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; + usesTabs = 0; }; 607FACD11AFB9204008FA782 /* Products */ = { isa = PBXGroup; @@ -268,6 +279,7 @@ 3D04B6D5211558B0006218A4 /* AccessibilityViewController.swift */, 3D04B6D72115592B006218A4 /* Accessibility Screens */, 607FACD31AFB9204008FA782 /* Supporting Files */, + 3FEF85492538455D0072611F /* SwiftUIView+EmbedInHostingController.swift */, ); name = AccessibilitySnapshotDemo; path = AccessibilitySnapshot; @@ -595,6 +607,7 @@ files = ( 3DC8D04C224734E400E8A03C /* DynamicTypeViewController.swift in Sources */, 607FACD81AFB9204008FA782 /* RootViewController.swift in Sources */, + 3F8E5DB92535B25000276B32 /* SwiftUIView.swift in Sources */, 3DBEAA5F2223D0DA00FAE61D /* AccessibilityPathViewController.swift in Sources */, 3D4674682116A3F100278B57 /* ViewAccessibilityPropertiesViewController.swift in Sources */, 3D39BFB22239BEA6009C3EF4 /* ActivationPointViewController.swift in Sources */, @@ -606,10 +619,12 @@ 3D04B6D921155942006218A4 /* LabelAccessibilityPropertiesViewController.swift in Sources */, 3DA12A3222405B9E00EB3C33 /* DataTableViewController.swift in Sources */, 3DEBF24E221018610065424F /* DefaultControlsViewController.swift in Sources */, + 3FEF854F253846420072611F /* SwiftUIViewWithScrollView.swift in Sources */, 3DF464FE220D594E0048D446 /* ElementSelectionViewController.swift in Sources */, 3DC488372212A7D4006D1E15 /* ModalAccessibilityViewController.swift in Sources */, 3DF46502220D7F9B0048D446 /* ElementOrderViewController.swift in Sources */, 3DDE7FF624C6D6BF00999ABA /* AccessibilityCustomActionsViewController.swift in Sources */, + 3FEF854A2538455D0072611F /* SwiftUIView+EmbedInHostingController.swift in Sources */, 3DBAC28F2242E7C700EF4D0A /* ListContainerViewController.swift in Sources */, 3DBEAA5B2222953E00FAE61D /* SwitchControlViewController.swift in Sources */, 3DBAC2912242F9B200EF4D0A /* LandmarkContainerViewController.swift in Sources */, @@ -839,6 +854,11 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; + OTHER_LDFLAGS = ( + "$(inherited)", + "-weak_framework", + SwiftUI, + ); PRODUCT_BUNDLE_IDENTIFIER = com.squareup.AccessibilitySnapshotDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; @@ -856,6 +876,11 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; + OTHER_LDFLAGS = ( + "$(inherited)", + "-weak_framework", + SwiftUI, + ); PRODUCT_BUNDLE_IDENTIFIER = com.squareup.AccessibilitySnapshotDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; diff --git a/Example/AccessibilitySnapshot/SwiftUIView+EmbedInHostingController.swift b/Example/AccessibilitySnapshot/SwiftUIView+EmbedInHostingController.swift new file mode 100644 index 00000000..a7272f01 --- /dev/null +++ b/Example/AccessibilitySnapshot/SwiftUIView+EmbedInHostingController.swift @@ -0,0 +1,28 @@ +// +// Copyright 2021 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import UIKit + +@available(iOS 13.0, *) +extension View { + + // This method should be removed once we support snapshotting native SwiftUI views. + // See cashapp/AccessibilitySnapshot#37. + func embedInHostingController() -> UIViewController { + return UIHostingController(rootView: self) + } +} diff --git a/Example/AccessibilitySnapshot/SwiftUIView.swift b/Example/AccessibilitySnapshot/SwiftUIView.swift new file mode 100644 index 00000000..e95ad0a1 --- /dev/null +++ b/Example/AccessibilitySnapshot/SwiftUIView.swift @@ -0,0 +1,92 @@ +// +// Copyright 2020 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 13.0, *) +fileprivate struct Circle: View { + var body: some View { + Rectangle() + .foregroundColor(Color(UIColor.lightGray)) + .frame(width: 30, height: 30) + .cornerRadius(15) + } +} + +@available(iOS 13.0, *) +struct SwiftUIView: View { + var body: some View { + VStack(spacing: 30) { + // View with nothing. + Circle() + .accessibility(label: Text("")) + .accessibility(value: Text("")) + .accessibility(hint: Text("")) + + // View with label. + Circle() + .accessibility(label: Text("Label")) + .accessibility(value: Text("")) + .accessibility(hint: Text("")) + + // View with value. + Circle() + .accessibility(label: Text("")) + .accessibility(value: Text("Value")) + .accessibility(hint: Text("")) + + // View with hint. + Circle() + .accessibility(label: Text("")) + .accessibility(value: Text("")) + .accessibility(hint: Text("Hint")) + + // View with label and value. + Circle() + .accessibility(label: Text("Label")) + .accessibility(value: Text("Value")) + .accessibility(hint: Text("")) + + // View with label and hint. + Circle() + .accessibility(label: Text("Label")) + .accessibility(value: Text("")) + .accessibility(hint: Text("Hint")) + + // View with value and hint. + Circle() + .accessibility(label: Text("")) + .accessibility(value: Text("Value")) + .accessibility(hint: Text("Hint")) + + // View with label, value, and hint. + Circle() + .accessibility(label: Text("Label")) + .accessibility(value: Text("Value")) + .accessibility(hint: Text("Hint")) + + Spacer() + } + } +} + +@available(iOS 13.0, *) +struct SwiftUIView_Previews: PreviewProvider { + static var previews: some View { + SwiftUIView() + .previewLayout(.sizeThatFits) + } +} diff --git a/Example/AccessibilitySnapshot/SwiftUIViewWithScrollView.swift b/Example/AccessibilitySnapshot/SwiftUIViewWithScrollView.swift new file mode 100644 index 00000000..08d43bfd --- /dev/null +++ b/Example/AccessibilitySnapshot/SwiftUIViewWithScrollView.swift @@ -0,0 +1,36 @@ +// +// Copyright 2020 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// A SwiftUI View inside a ScrollView will produce no accessibility elements for iOS 14.0 and 14.1, for more +/// information see cashapp/AccessibilitySnapshot#33. This seems to be a SwiftUI bug which was resolved in iOS 14.2. +@available(iOS 13.0, *) +struct SwiftUIViewWithScrollView: View { + var body: some View { + ScrollView { + SwiftUIView() + } + } +} + +@available(iOS 13.0, *) +struct SwiftUIViewWithScrollView_Previews: PreviewProvider { + static var previews: some View { + SwiftUIViewWithScrollView() + .previewLayout(.sizeThatFits) + } +} diff --git a/Example/SnapshotTests/SnapshotTestingTests.swift b/Example/SnapshotTests/SnapshotTestingTests.swift index 2cb40e2d..be722537 100644 --- a/Example/SnapshotTests/SnapshotTestingTests.swift +++ b/Example/SnapshotTests/SnapshotTestingTests.swift @@ -25,6 +25,30 @@ final class SnapshotTestingTests: XCTestCase { // MARK: - Tests + @available(iOS 13.0, *) + func testSimpleSwiftUIConfiguration() throws { + guard isOperatingSystemAtLeast13() else { + print("SwiftUI Views are only supported with iOS 13 or later.") + return + } + + let viewController = SwiftUIView().embedInHostingController() + viewController.view.frame = UIScreen.main.bounds + assertSnapshot(matching: viewController, as: .accessibilityImage, named: nameForDevice()) + } + + @available(iOS 13.0, *) + func testSimpleSwiftUIWithScrollViewConfiguration() throws { + guard isOperatingSystemAtLeast13() else { + print("SwiftUI Views are only supported with iOS 13 or later.") + return + } + + let viewController = SwiftUIViewWithScrollView().embedInHostingController() + viewController.view.frame = UIScreen.main.bounds + assertSnapshot(matching: viewController, as: .accessibilityImage, named: nameForDevice()) + } + func testSimpleConfiguration() { let viewController = ViewAccessibilityPropertiesViewController() viewController.view.frame = UIScreen.main.bounds @@ -135,4 +159,19 @@ final class SnapshotTestingTests: XCTestCase { .joined(separator: "-") } + /// To check if the operating system is at least iOS 13. + /// + /// This is needed to be able to skip tests which don't support SwiftUI for + /// example, because an `@available` check for the test case isn't enough to + /// skip the test on older iOS versions. + /// - Returns: A boolean whether the system is at least iOS 13 or not. + private func isOperatingSystemAtLeast13() -> Bool { + let iOS13 = OperatingSystemVersion( + majorVersion: 13, + minorVersion: 0, + patchVersion: 0 + ) + return ProcessInfo().isOperatingSystemAtLeast(iOS13) + } + } diff --git a/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.375x812-13-3-3x.png b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.375x812-13-3-3x.png new file mode 100644 index 00000000..aa23c3f2 Binary files /dev/null and b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.375x812-13-3-3x.png differ diff --git a/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.390x844-14-2-3x.png b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.390x844-14-2-3x.png new file mode 100644 index 00000000..53f643c0 Binary files /dev/null and b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIConfiguration.390x844-14-2-3x.png differ diff --git a/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.375x812-13-3-3x.png b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.375x812-13-3-3x.png new file mode 100644 index 00000000..aa23c3f2 Binary files /dev/null and b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.375x812-13-3-3x.png differ diff --git a/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.390x844-14-2-3x.png b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.390x844-14-2-3x.png new file mode 100644 index 00000000..53f643c0 Binary files /dev/null and b/Example/SnapshotTests/__Snapshots__/SnapshotTestingTests/testSimpleSwiftUIWithScrollViewConfiguration.390x844-14-2-3x.png differ