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:
parent
f246b0715c
commit
461fa7b608
|
@ -7,6 +7,7 @@ Changelog
|
||||||
- Add Github Action
|
- Add Github Action
|
||||||
- Added `.introspectTextView()`.
|
- Added `.introspectTextView()`.
|
||||||
- Update CircleCI config to use Xcode 12.4.0
|
- Update CircleCI config to use Xcode 12.4.0
|
||||||
|
- Fixed nested `ScrollView` detection on iOS 14 and macOS 11
|
||||||
|
|
||||||
## [0.1.2]
|
## [0.1.2]
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,13 @@ public enum TargetViewSelector {
|
||||||
}
|
}
|
||||||
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
|
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? {
|
public static func siblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
|
||||||
guard let viewHost = Introspect.findViewHost(from: entry) else {
|
guard let viewHost = Introspect.findViewHost(from: entry) else {
|
||||||
|
@ -251,7 +258,14 @@ public enum TargetViewSelector {
|
||||||
}
|
}
|
||||||
return Introspect.previousSibling(ofType: TargetView.self, from: viewHost)
|
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? {
|
public static func ancestorOrSiblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
|
||||||
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
|
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
|
||||||
return tableView
|
return tableView
|
||||||
|
|
|
@ -83,9 +83,9 @@ extension View {
|
||||||
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
|
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
|
||||||
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
|
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
|
||||||
if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) {
|
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 {
|
} 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.
|
/// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
|
||||||
public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View {
|
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`
|
/// Finds a `NSTextField` from a `SwiftUI.TextField`
|
||||||
|
|
|
@ -55,8 +55,8 @@ private struct ListTestView: View {
|
||||||
@available(macOS 10.15.0, *)
|
@available(macOS 10.15.0, *)
|
||||||
private struct ScrollTestView: View {
|
private struct ScrollTestView: View {
|
||||||
|
|
||||||
let spy1: () -> Void
|
let spy1: (NSScrollView) -> Void
|
||||||
let spy2: () -> Void
|
let spy2: (NSScrollView) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -64,13 +64,39 @@ private struct ScrollTestView: View {
|
||||||
Text("Item 1")
|
Text("Item 1")
|
||||||
}
|
}
|
||||||
.introspectScrollView { scrollView in
|
.introspectScrollView { scrollView in
|
||||||
self.spy1()
|
self.spy1(scrollView)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Text("Item 1")
|
Text("Item 1")
|
||||||
.introspectScrollView { scrollView in
|
.introspectScrollView { scrollView in
|
||||||
self.spy2()
|
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)
|
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testScrollView() {
|
func testScrollView() throws {
|
||||||
|
|
||||||
let expectation1 = XCTestExpectation()
|
let expectation1 = XCTestExpectation()
|
||||||
let expectation2 = XCTestExpectation()
|
let expectation2 = XCTestExpectation()
|
||||||
|
|
||||||
|
var scrollView1: NSScrollView?
|
||||||
|
var scrollView2: NSScrollView?
|
||||||
|
|
||||||
let view = ScrollTestView(
|
let view = ScrollTestView(
|
||||||
spy1: { expectation1.fulfill() },
|
spy1: { scrollView in
|
||||||
spy2: { expectation2.fulfill() }
|
scrollView1 = scrollView
|
||||||
|
expectation1.fulfill() },
|
||||||
|
spy2: { scrollView in
|
||||||
|
scrollView2 = scrollView
|
||||||
|
expectation2.fulfill()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
TestUtils.present(view: view)
|
TestUtils.present(view: view)
|
||||||
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
|
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() {
|
func testTextField() {
|
||||||
|
|
||||||
let expectation = XCTestExpectation()
|
let expectation = XCTestExpectation()
|
||||||
|
|
|
@ -139,8 +139,8 @@ private struct ListTestView: View {
|
||||||
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
|
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
|
||||||
private struct ScrollTestView: View {
|
private struct ScrollTestView: View {
|
||||||
|
|
||||||
let spy1: () -> Void
|
let spy1: (UIScrollView) -> Void
|
||||||
let spy2: () -> Void
|
let spy2: (UIScrollView) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -148,18 +148,43 @@ private struct ScrollTestView: View {
|
||||||
Text("Item 1")
|
Text("Item 1")
|
||||||
}
|
}
|
||||||
.introspectScrollView { scrollView in
|
.introspectScrollView { scrollView in
|
||||||
self.spy1()
|
self.spy1(scrollView)
|
||||||
}
|
}
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Text("Item 1")
|
Text("Item 1")
|
||||||
.introspectScrollView { scrollView in
|
.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, *)
|
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
|
||||||
private struct TextFieldTestView: View {
|
private struct TextFieldTestView: View {
|
||||||
let spy: () -> Void
|
let spy: () -> Void
|
||||||
|
@ -316,18 +341,60 @@ class UIKitTests: XCTestCase {
|
||||||
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
|
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testScrollView() {
|
func testScrollView() throws {
|
||||||
|
|
||||||
let expectation1 = XCTestExpectation()
|
let expectation1 = XCTestExpectation()
|
||||||
let expectation2 = XCTestExpectation()
|
let expectation2 = XCTestExpectation()
|
||||||
|
|
||||||
|
var scrollView1: UIScrollView?
|
||||||
|
var scrollView2: UIScrollView?
|
||||||
|
|
||||||
let view = ScrollTestView(
|
let view = ScrollTestView(
|
||||||
spy1: { expectation1.fulfill() },
|
spy1: { scrollView in
|
||||||
spy2: { expectation2.fulfill() }
|
scrollView1 = scrollView
|
||||||
|
expectation1.fulfill()
|
||||||
|
},
|
||||||
|
spy2: { scrollView in
|
||||||
|
scrollView2 = scrollView
|
||||||
|
expectation2.fulfill()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
TestUtils.present(view: view)
|
TestUtils.present(view: view)
|
||||||
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
|
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() {
|
func testTextField() {
|
||||||
|
|
||||||
let expectation = XCTestExpectation()
|
let expectation = XCTestExpectation()
|
||||||
|
|
Loading…
Reference in New Issue