Skip to content

Commit

Permalink
Add API misuse checks for find functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Naumov committed Dec 24, 2022
1 parent 89d26e6 commit 9c68922
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ identifier_name:
min_length: 2

file_length:
warning: 500
warning: 600
error: 1000

function_parameter_count:
Expand Down
8 changes: 2 additions & 6 deletions Sources/ViewInspector/ContentExtraction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions Sources/ViewInspector/Inspector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 16 additions & 0 deletions Sources/ViewInspector/SwiftUI/CustomView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ internal extension Content {
public extension InspectableView where View: SingleViewContent {

func view<T>(_ type: T.Type) throws -> InspectableView<ViewType.View<T>> 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<T>.inspectionCall(typeName: Inspector.typeName(type: type))
Expand All @@ -88,6 +96,14 @@ public extension InspectableView where View: SingleViewContent {
public extension InspectableView where View: MultipleViewContent {

func view<T>(_ type: T.Type, _ index: Int) throws -> InspectableView<ViewType.View<T>> 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<T>.inspectionCall(typeName: Inspector.typeName(type: type))
Expand Down
26 changes: 18 additions & 8 deletions Sources/ViewInspector/ViewSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<V>(_ inspectable: V.Type,
func find<V>(_ customViewType: V.Type,
relation: ViewSearch.Relation = .child,
where condition: (InspectableView<ViewType.View<V>>) throws -> Bool = { _ in true }
) throws -> InspectableView<ViewType.View<V>> 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<V>.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<V>(_ inspectable: V.Type,
func find<V>(_ customViewType: V.Type,
containing string: String,
locale: Locale = .testsDefault
) throws -> InspectableView<ViewType.View<V>> {
) throws -> InspectableView<ViewType.View<V>> 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<V>.self, containing: string, locale: locale)
}

Expand All @@ -210,7 +220,7 @@ public extension InspectableView {
func find<T>(_ viewType: T.Type,
containing string: String,
locale: Locale = .testsDefault
) throws -> InspectableView<T> {
) throws -> InspectableView<T> 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
Expand Down Expand Up @@ -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<V>(_ inspectable: V.Type,
func findAll<V>(_ customViewType: V.Type,
where condition: (InspectableView<ViewType.View<V>>) throws -> Bool = { _ in true }
) -> [InspectableView<ViewType.View<V>>] where V: SwiftUI.View {
return findAll(ViewType.View<V>.self, where: condition)
Expand Down
18 changes: 17 additions & 1 deletion Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 9c68922

Please sign in to comment.