-
Notifications
You must be signed in to change notification settings - Fork 157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add inspection support for ViewThatFits. #255
Add inspection support for ViewThatFits. #255
Conversation
static func child(_ content: Content) throws -> Content { | ||
let view: Any = try { | ||
guard let rootContent = try? Inspector.attribute(path: "_tree|content", value: content.view) else { | ||
// A ViewThatFits View renders only one of its child Views based on the available horizontal space. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment was added to explicitly document that we currently return all children of a ViewThatFits
View.
cc: @bachand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inspector.attribute(path: "_tree|content", value: content.view)
is what will return all children right? If so I wonder if this comment should go above that line instead of in the else
of the guard
statement.
let shortText = try sut.inspect().find(text: "Short text") | ||
|
||
XCTAssertEqual(try longText.string(), "Very long text that will only get picked if there is available space.") | ||
XCTAssertEqual(try shortText.string(), "Short text") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test intentionally validates that both children Views are inspectable even though in a real runtime scenario only one of them would be provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thank you for unblocking inspection of ViewThatFits
, which is used in some critical views within our codebase. Will be curious to see what @nalexn thinks about this change and about whether it makes sense to provide further customization options during inspection (e.g. an option to return the first child of ViewThatFits
, instead of all children).
import SwiftUI | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
internal extension ViewType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be public? For comparison:
ViewInspector/Sources/ViewInspector/SwiftUI/VStack.swift
Lines 4 to 9 in 0f872b5
public extension ViewType { | |
struct VStack: KnownViewType { | |
public static let typePrefix: String = "VStack" | |
} | |
} |
static func child(_ content: Content) throws -> Content { | ||
let view: Any = try { | ||
guard let rootContent = try? Inspector.attribute(path: "_tree|content", value: content.view) else { | ||
// A ViewThatFits View renders only one of its child Views based on the available horizontal space. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// A ViewThatFits View renders only one of its child Views based on the available horizontal space. | |
// A ViewThatFits View renders only one of its child Views based on the available space. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static func child(_ content: Content) throws -> Content { | ||
let view: Any = try { | ||
guard let rootContent = try? Inspector.attribute(path: "_tree|content", value: content.view) else { | ||
// A ViewThatFits View renders only one of its child Views based on the available horizontal space. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inspector.attribute(path: "_tree|content", value: content.view)
is what will return all children right? If so I wonder if this comment should go above that line instead of in the else
of the guard
statement.
// A ViewThatFits View renders only one of its child Views based on the available horizontal space. | ||
// This inspection returns all children that can potentially be picked by `ViewThatFits`. | ||
// https://developer.apple.com/documentation/swiftui/viewthatfits | ||
return try Inspector.attribute(path: "content", value: content.view) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you help me understand this fallback? When would we expect this to be encountered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we had similar code in #228 but I want to make sure it's necessary here.
|
||
return rootContent | ||
}() | ||
return try Inspector.unwrap(view: view, medium: content.medium) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nalexn can you help me better understand what it's appropriate to invoke this unwrap(…)
method? I am trying to understand if we should always be unwrapping when extracting content from views.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap
should be called when you get to a user-provided view that might be "wrapped" in modifiers. For example, AnyView(x)
. This x
can be a standard view, a custom user-defined view, or either of these but "wrapped" in a modifier, such as x.padding(10)
. "unwrap" makes sure the root view of "x" is revealed for ease of further inspection (but modifiers are remembered as well, just stored separately).
On the other hand, when you inspect an internal child of a standard SwiftUI view, there is no need to "unwrap" them, if you can see from the code there is no generic parameter that can alter the view and turn it from Text
into a ModifiedContent<Text>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the explanation!
@@ -0,0 +1,26 @@ | |||
import XCTest | |||
import SwiftUI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import SwiftUI | |
import SwiftUI | |
@testable import ViewInspector |
Thanks! I'll review the PR as soon as possible. As a side note, I feel like I've already given you this tip, but instead of |
Thank you @nalexn for making time to review it. We appreciate your efforts here! |
Changes
Similar to the issue fixed by #228, this change aims to fix blocking inspection for Views that are nested inside a ViewThatFits View.
ViewThatFits
has a structure similar to aTreeView
and needs to be inspected for itscontent
property for ViewInspector to find its children.Discussion
This is a first step that unblocks the inspection of children Views of
ViewThatFits
.However, the documented behavior of this View states that only one of its children will be provided (rendered) based on horizontal space. Implementing inspection in a way that all children Views are returned may be problematic as all children are likely going to be similar in content, labels and accessibility traits.
This can potentially cause multiple Views returned in an inspection done with a
findAll(where:)
call.I will investigate the possibility of hosting the
ViewThatFits
View inside aViewHosting
View and validate whether only one of the children view is available. That will be the scenario that is the closest to SwiftUI running on a real simulator/device.Another suggestion we're considering is the addition of a capability that enables the test code to pick an index of the child view wanted. Alternatively we can just always default to the first child in the
ViewThatFits
.@nalexn we'd love to have your thoughts and recommendations on this.
Testing
I added a test to explicitly find 2 children Views inside a
ViewThatFits
, which enforces the behavior being proposed in this PR.Review
@bachand
@nalexn