Fix an issue where nested ScrollViews would not be detected properly (#69)

* Fix an issue where nested ScrollViews would not be detected properly
* Fix nested ScrollView issue on iOS 13
* Clean up implementation of siblingOrAncestorOfType to avoid extraneous searches
* Adjust naming for correctness
* Add nested ScrollView tests for macOS
This also fixes the same issue iOS had, where nested ScrollViews would not be correctly detected on macOS 11+
* Update UIKit ScrollView tests to match AppKit ones
* Add changelog entry for nested scrollview fixes
* Change NSScrollView lookup mechanism for macOS 10.15 to match UIKit behavior
This commit is contained in:
Simon Jarbrant 2021-03-14 12:33:01 +01:00 committed by GitHub
parent f246b0715c
commit 461fa7b608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 174 additions and 21 deletions

View File

@ -7,6 +7,7 @@ Changelog
- Add Github Action
- Added `.introspectTextView()`.
- Update CircleCI config to use Xcode 12.4.0
- Fixed nested `ScrollView` detection on iOS 14 and macOS 11
## [0.1.2]

View File

@ -244,6 +244,13 @@ public enum TargetViewSelector {
}
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
}
public static func siblingContainingOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let sibling: TargetView = siblingContaining(from: entry) {
return sibling
}
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
}
public static func siblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
guard let viewHost = Introspect.findViewHost(from: entry) else {
@ -251,7 +258,14 @@ public enum TargetViewSelector {
}
return Introspect.previousSibling(ofType: TargetView.self, from: viewHost)
}
public static func siblingOfTypeOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let sibling: TargetView = siblingOfType(from: entry) {
return sibling
}
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
}
public static func ancestorOrSiblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
return tableView

View File

@ -83,9 +83,9 @@ extension View {
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) {
return introspect(selector: TargetViewSelector.ancestorOrSiblingOfType, customize: customize)
return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize)
} else {
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize)
}
}
@ -157,7 +157,11 @@ extension View {
/// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View {
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
if #available(macOS 11.0, *) {
return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize)
} else {
return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize)
}
}
/// Finds a `NSTextField` from a `SwiftUI.TextField`

View File

@ -55,8 +55,8 @@ private struct ListTestView: View {
@available(macOS 10.15.0, *)
private struct ScrollTestView: View {
let spy1: () -> Void
let spy2: () -> Void
let spy1: (NSScrollView) -> Void
let spy2: (NSScrollView) -> Void
var body: some View {
HStack {
@ -64,13 +64,39 @@ private struct ScrollTestView: View {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy1()
self.spy1(scrollView)
}
ScrollView {
Text("Item 1")
.introspectScrollView { scrollView in
self.spy2()
.introspectScrollView { scrollView in
self.spy2(scrollView)
}
}
}
}
}
@available(macOS 10.15.0, *)
private struct NestedScrollTestView: View {
let spy1: (NSScrollView) -> Void
let spy2: (NSScrollView) -> Void
var body: some View {
HStack {
ScrollView {
Text("Item 1")
ScrollView {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy2(scrollView)
}
}
.introspectScrollView { scrollView in
self.spy1(scrollView)
}
}
}
@ -175,18 +201,59 @@ class AppKitTests: XCTestCase {
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
}
func testScrollView() {
func testScrollView() throws {
let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()
var scrollView1: NSScrollView?
var scrollView2: NSScrollView?
let view = ScrollTestView(
spy1: { expectation1.fulfill() },
spy2: { expectation2.fulfill() }
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill() },
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}
func testNestedScrollView() throws {
let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()
var scrollView1: NSScrollView?
var scrollView2: NSScrollView?
let view = NestedScrollTestView(
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}
func testTextField() {
let expectation = XCTestExpectation()

View File

@ -139,8 +139,8 @@ private struct ListTestView: View {
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct ScrollTestView: View {
let spy1: () -> Void
let spy2: () -> Void
let spy1: (UIScrollView) -> Void
let spy2: (UIScrollView) -> Void
var body: some View {
HStack {
@ -148,18 +148,43 @@ private struct ScrollTestView: View {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy1()
self.spy1(scrollView)
}
ScrollView {
Text("Item 1")
.introspectScrollView { scrollView in
self.spy2()
self.spy2(scrollView)
}
}
}
}
}
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct NestedScrollTestView: View {
let spy1: (UIScrollView) -> Void
let spy2: (UIScrollView) -> Void
var body: some View {
HStack {
ScrollView(showsIndicators: true) {
Text("Item 1")
ScrollView(showsIndicators: false) {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy2(scrollView)
}
}
.introspectScrollView { scrollView in
self.spy1(scrollView)
}
}
}
}
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct TextFieldTestView: View {
let spy: () -> Void
@ -316,18 +341,60 @@ class UIKitTests: XCTestCase {
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
}
func testScrollView() {
func testScrollView() throws {
let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()
var scrollView1: UIScrollView?
var scrollView2: UIScrollView?
let view = ScrollTestView(
spy1: { expectation1.fulfill() },
spy2: { expectation2.fulfill() }
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}
func testNestedScrollView() throws {
let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()
var scrollView1: UIScrollView?
var scrollView2: UIScrollView?
let view = NestedScrollTestView(
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}
func testTextField() {
let expectation = XCTestExpectation()