diff --git a/.swiftlint.yml b/.swiftlint.yml index 366b5ac6..8ff3e7bf 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -39,7 +39,7 @@ identifier_name: min_length: 2 file_length: - warning: 500 + warning: 600 error: 1000 function_parameter_count: diff --git a/Sources/ViewInspector/ContentExtraction.swift b/Sources/ViewInspector/ContentExtraction.swift index e9c75b10..4ff2669f 100644 --- a/Sources/ViewInspector/ContentExtraction.swift +++ b/Sources/ViewInspector/ContentExtraction.swift @@ -24,12 +24,8 @@ internal struct ContentExtractor { switch source { case let view as any View: guard !Inspector.isSystemType(value: view) else { - let name = Inspector.typeName(value: view) - throw InspectionError.notSupported( - """ - Please replace .view(\(name).self) inspection call with \ - .\(name.firstLetterLowercased)() or .find(ViewType.\(name).self) - """) + let name = Inspector.typeName(value: source) + throw InspectionError.notSupported("Not a custom view type: \(name)") } return .view(view) case let viewModifier as any ViewModifier: diff --git a/Sources/ViewInspector/Inspector.swift b/Sources/ViewInspector/Inspector.swift index 7f6f8f51..584f9160 100644 --- a/Sources/ViewInspector/Inspector.swift +++ b/Sources/ViewInspector/Inspector.swift @@ -56,6 +56,15 @@ internal extension Inspector { static func isSystemType(value: Any) -> Bool { let name = typeName(value: value, namespaced: true) + return isSystemType(name: name) + } + + static func isSystemType(type: Any.Type) -> Bool { + let name = typeName(type: type, namespaced: true) + return isSystemType(name: name) + } + + private static func isSystemType(name: String) -> Bool { return [ "SwiftUI", "CoreLocationUI", "MapKit", "AuthenticationServices", "AVKit", diff --git a/Sources/ViewInspector/SwiftUI/CustomView.swift b/Sources/ViewInspector/SwiftUI/CustomView.swift index 9b5198ce..9f9ad4dd 100644 --- a/Sources/ViewInspector/SwiftUI/CustomView.swift +++ b/Sources/ViewInspector/SwiftUI/CustomView.swift @@ -73,6 +73,14 @@ internal extension Content { public extension InspectableView where View: SingleViewContent { func view(_ type: T.Type) throws -> InspectableView> where T: SwiftUI.View { + guard !Inspector.isSystemType(type: type) else { + let name = Inspector.typeName(type: type) + throw InspectionError.notSupported( + """ + Please replace .view(\(name).self) inspection call with \ + .\(name.firstLetterLowercased)() or .find(ViewType.\(name).self) + """) + } let child = try View.child(content) let prefix = Inspector.typeName(type: type, namespaced: true, generics: .remove) let base = ViewType.View.inspectionCall(typeName: Inspector.typeName(type: type)) @@ -88,6 +96,14 @@ public extension InspectableView where View: SingleViewContent { public extension InspectableView where View: MultipleViewContent { func view(_ type: T.Type, _ index: Int) throws -> InspectableView> where T: SwiftUI.View { + guard !Inspector.isSystemType(type: type) else { + let name = Inspector.typeName(type: type) + throw InspectionError.notSupported( + """ + Please replace .view(\(name).self, \(index)) inspection \ + call with .\(name.firstLetterLowercased)(\(index)) + """) + } let content = try child(at: index) let prefix = Inspector.typeName(type: type, namespaced: true, generics: .remove) let base = ViewType.View.inspectionCall(typeName: Inspector.typeName(type: type)) diff --git a/Sources/ViewInspector/ViewSearch.swift b/Sources/ViewInspector/ViewSearch.swift index 19a20d45..c176a285 100644 --- a/Sources/ViewInspector/ViewSearch.swift +++ b/Sources/ViewInspector/ViewSearch.swift @@ -166,34 +166,44 @@ public extension InspectableView { /** Searches for a view of a specific type that matches a given condition - - Parameter inspectable: Your custom `Inspectable` view type. For example: `ContentView.self` + - Parameter customViewType: Your custom view type. For example: `ContentView.self` - Parameter relation: The direction of the search. Defaults to `.child` - Parameter where: The condition closure for detecting a matching view. Thrown errors are interpreted as "this view does not match" - Throws: An error if the view cannot be found - Returns: A found view */ - func find(_ inspectable: V.Type, + func find(_ customViewType: V.Type, relation: ViewSearch.Relation = .child, where condition: (InspectableView>) throws -> Bool = { _ in true } ) throws -> InspectableView> where V: SwiftUI.View { + guard !Inspector.isSystemType(type: customViewType) else { + let name = Inspector.typeName(type: customViewType) + throw InspectionError.notSupported( + "Please use .find(ViewType.\(name).self) instead of .find(\(name).self) inspection call.") + } return try find(ViewType.View.self, relation: relation, where: condition) } /** Searches for a view of a specific type, which enclosed hierarchy contains a `Text` with the provided string - - Parameter inspectable: Your custom `Inspectable` view type. For example: `ContentView.self` + - Parameter customViewType: Your custom view type. For example: `ContentView.self` - Parameter containing: The string to look up for - Parameter locale: The locale for the text extraction. Defaults to `testsDefault` (i.e. `Locale(identifier: "en")`) - Throws: An error if the view cannot be found - Returns: A found view */ - func find(_ inspectable: V.Type, + func find(_ customViewType: V.Type, containing string: String, locale: Locale = .testsDefault - ) throws -> InspectableView> { + ) throws -> InspectableView> where V: SwiftUI.View { + guard !Inspector.isSystemType(type: customViewType) else { + let name = Inspector.typeName(type: customViewType) + throw InspectionError.notSupported( + "Please use .find(ViewType.\(name).self) instead of .find(\(name).self) inspection call.") + } return try find(ViewType.View.self, containing: string, locale: locale) } @@ -210,7 +220,7 @@ public extension InspectableView { func find(_ viewType: T.Type, containing string: String, locale: Locale = .testsDefault - ) throws -> InspectableView { + ) throws -> InspectableView where T: KnownViewType { return try find(ViewType.Text.self, where: { text in try text.string(locale: locale) == string && (try? text.find(T.self, relation: .parent)) != nil @@ -272,12 +282,12 @@ public extension InspectableView { The hierarchy is traversed in depth-first order, meaning that you'll get views ordered top-to-bottom as they appear in the code, regardless of their nesting depth. - - Parameter inspectable: Your custom `Inspectable` view type. For example: `ContentView.self` + - Parameter customViewType: Your custom view type. For example: `ContentView.self` - Parameter where: The condition closure for detecting a matching view. Thrown errors are interpreted as "this view does not match" - Returns: An array of all matching views or an empty array if none are found. */ - func findAll(_ inspectable: V.Type, + func findAll(_ customViewType: V.Type, where condition: (InspectableView>) throws -> Bool = { _ in true } ) -> [InspectableView>] where V: SwiftUI.View { return findAll(ViewType.View.self, where: condition) diff --git a/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift b/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift index 896afbea..41156bd5 100644 --- a/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift +++ b/Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift @@ -77,12 +77,28 @@ final class CustomViewTests: XCTestCase { func testCustomViewAPIMisuseError() throws { let sut = try SimpleTestView().inspect().view(SimpleTestView.self) XCTAssertNoThrow(try sut.emptyView()) + XCTAssertNoThrow(try sut.emptyView(0)) XCTAssertNoThrow(try sut.find(ViewType.EmptyView.self)) - XCTAssertThrows(try sut.view(EmptyView.self).text(), + XCTAssertThrows(try sut.find(EmptyView.self), + """ + Please use .find(ViewType.EmptyView.self) instead \ + of .find(EmptyView.self) inspection call. + """) + XCTAssertThrows(try sut.find(EmptyView.self, containing: "abc"), + """ + Please use .find(ViewType.EmptyView.self) instead \ + of .find(EmptyView.self) inspection call. + """) + XCTAssertThrows(try sut.view(EmptyView.self), """ Please replace .view(EmptyView.self) inspection call \ with .emptyView() or .find(ViewType.EmptyView.self) """) + XCTAssertThrows(try sut.view(EmptyView.self, 0), + """ + Please replace .view(EmptyView.self, 0) inspection \ + call with .emptyView(0) + """) } func testEnvViewResetsModifiers() throws {