nalexn / viewinspector Goto Github PK
View Code? Open in Web Editor NEWRuntime introspection and unit testing of SwiftUI views
License: MIT License
Runtime introspection and unit testing of SwiftUI views
License: MIT License
I have redux architecture on project and I use store
`import Combine
final class Store<State, Action>: ObservableObject {
@Published private(set) var state: State
private let reducer: Reducer<State, Action>
private var cancellables: Set<AnyCancellable> = []
init(initialState: State, reducer: @escaping Reducer<State, Action>) {
self.state = initialState
self.reducer = reducer
}
func send(_ action: Action) {
guard let effect = reducer(&state, action) else {
return
}
effect
.receive(on: DispatchQueue.main)
.sink(receiveValue: send)
.store(in: &cancellables)
}
}`
and than I use it on View
` @EnvironmentObject var store: Store<AppState, AppAction>
func containedView() -> some View {
let index = store.state.id < 1 ? 0 : store.state.id > offsets.count ? 0 : store.state.id - 1
let offset = offsets[index]
return AnyView(SomeView(index + 1, offsetX: offset.x, offsetY: offset.y))
}
`
After when I write test for example:
`func testViewsCorrectInitialState() throws {
var sut = SomeView()
let backgroundImage = try sut.body.inspect().zStack().image(0).imageName()
XCTAssertEqual(backgroundImage, "background")
let text = try sut.body.inspect().geometryReader().zStack().text(1).string()
XCTAssertEqual(text, "text".localized)
}`
I have an error on build VIew:
Thread 1: Fatal error: No ObservableObject of type Store<AppState, AppAction> found. A View.environmentObject(_:) for Store<AppState, AppAction> may be missing as an ancestor of this view.
How I can resolve it?
I have a use case in which I need to concatenate Text
views, and I want to inspect the result of that, but ViewInspector throws an error in that case. You can see a really simple example reproducing the issue below:
Test | Result |
---|---|
func testSingleText() throws { let sut = Text("ab") let text = try sut.inspect().text().string() XCTAssertEqual("ab", text) } |
Test Case '-[TargetTests.SwiftUIViewInspectorTests testSingleText]' started. Test Case '-[TargetTests.SwiftUIViewInspectorTests testSingleText]' passed (0.007 seconds). |
func testConcatenatedText() throws { let sut = Text("a") + Text("b") let text = try sut.inspect().text().string() XCTAssertEqual("ab", text) } |
Test Case '-[TargetTests.SwiftUIViewInspectorTests testConcatenatedText]' started. /path/to/project/Pods/ViewInspector/Sources/ViewInspector/Inspector.swift:17: error: -[TargetTests.SwiftUIViewInspectorTests testConcatenatedText] : failed: caught error: "attributeNotFound(label: "key", type: "ConcatenatedTextStorage")" Test Case '-[TargetTests.SwiftUIViewInspectorTests testConcatenatedText]' failed (0.322 seconds). |
Test details:
ViewInspector version: 0.3.8 (installed with Cocoapods)
Cocoapods version: 1.6.1
Swift version: 5
Xcode version: 11.5 (11E608c)
macOS version: 10.15.6
Given the following view:
struct ContentView: View {
let showConditionalView = false
var body: some View {
Button(action: {}) {
HStack(alignment: .center) {
if showConditionalView {
Rectangle()
}
Text("Foo")
}
}
}
}
This test will fail
extension ContentView: Inspectable { }
class Example: XCTestCase {
func testExample() throws {
let view = ContentView()
let res = try view.inspect().findAll(ViewType.Text.self)
XCTAssertGreaterThan(res.count, 0)
}
}
find
and findAll
will not locate the Text
view.
Manually inspecting the view hierarchy explicitly under the HStack like this will work: view.inspect().findAll(ViewType.HStack.self).text(1)
. But of course that defeats the purpose of dynamic query.
If you comment out the conditional before the Text("Foo")
the test case will correctly locate that view and pass.
I wanna test that modal presentation by tapping button. (Like below code)
using ViewInspector, How can i write test code?
struct ContentView: View {
@State var isActiveSubView = false
var body: some View {
NavigationView {
VStack {
Button(action: {
self.isActiveSubView.toggle()
}) {
Text("Show Modal")
}.sheet(isPresented: $isActiveSubView) {
SubView()
}
}
}
}
}
Hello,
I have some unclear issue.
When I run app, it's crashes and in logs I found here:
dyld: Library not loaded: @rpath/XCTest.framework/XCTest
Referenced from: /Users/xxx/Library/Developer/CoreSimulator/Devices/1323FEEC-8B65-43C1-9EA5-8E09E6242D36/data/Containers/Bundle/Application/CAA0CC5F-F5E4-48AA-A0D2-92DF04533468/Xxx.app/Xxx
Reason: image not found
And it's happening when I add pod 'ViewInspector'
in Podfile.
If I remove pod 'ViewInspector'
after pod installing, run app - it's OK.
With other pods app runs also ok.
This is supported from another SwiftUI testing framework:
https://github.com/siteline/SwiftUI-Introspect
It use the UIKit/AppKit view hierarchy to search the view end with ViewHost
.
Maybe we can have such a API?
func uiView<T>(_ type: T.Type) throws -> T.UIViewType
where T: Inspectable & UIViewRepresentable
func nsView<T>(_ type: T.Type) throws -> T.NSViewType
where T: Inspectable & NSViewRepresentable
Following test will fail now:
private struct TestView2: View, Inspectable {
let flag: Bool
var body: some View {
HStack {
Text("ABC")
if flag { Text("XYZ") }
}
}
}
final class OptionalViewTests: XCTestCase {
func testNonOptionalViewWhenOtherIsMissing() throws {
let view = TestView2(flag: false)
XCTAssertNoThrow(try view.inspect().hStack().text(0))
}
}
It happens because the "XYZ" view is optional AND nil by the time it's being inspected, so the Inspector throws a InspectionError.viewNotFound
error. I don't think there is a simple way or a workaround to fix it :(
What is needed is that
extension Optional: OptionalViewContentProvider {
func content() throws -> Any {
switch self {
case let .some(view):
return try Inspector.unwrap(view: view)
case .none:
throw InspectionError.viewNotFound(parent: Inspector.typeName(value: self as Any))
}
}
}
this content()
method could return nil in the second case, but that in turn will make other tests fail as it will not be working correctly for inspecting optional views that are expected to exist but do not :(
EDIT: Well, the only workaround for now actually is making sure all the optional views are existing (are non-nil) before inspecting them.
Hi Alex, thanks for building an awesome library!
There seems to be something wrong going on with the Iterators.
This snippet causes an index out of range exception:
let sut = ForEach([0, 1], id:\.self) { id in
Text("\(id)")
}
try sut.inspect().forEach().forEach {
print($0)
}
For example, I have the type InspectableView<ViewType.View<TupleView<(AnyView, AnyView?)>>>, I didn't find a way to get each view to check some properties if it is defined as the tap gesture.
GeometryProxy uses since Xcode12 52 bytes instead of 48. The precondition fails for Allocator (which uses 48) in GeometryProxy extension.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
private extension GeometryProxy {
struct Allocator {
let data: (Int64, Int64, Int64, Int64, Int64, Int64) = (0, 0, 0, 0, 0, 0)
}
static func stub() -> GeometryProxy {
precondition(MemoryLayout<GeometryProxy>.size == MemoryLayout<Allocator>.size)
return unsafeBitCast(Allocator(), to: GeometryProxy.self)
}
}
Xcode version: 11.4
Swift version: 5.1
Cocoapods version: 1.9.1
Hello there!!
I'm trying add this package into my project to SwiftUI
tests targets, and I've found the error below:
I tried use this package with Swift package manager and with Cocoapods, and the library is not found with both package managers. I've tried import this package in two targets: Unit and UI Tests, and the problem is the same for both.
I've added another library to my Xcode 11
project/workspace and works great!
Do you know what I'm doing wrong?
I'm running into some problems inspecting views with @EnvironmentObject
. Namely, my tests crash with the following error:
Fatal error: No ObservableObject of type Validation found.
A View.environmentObject(_:) for Validation may be missing as an ancestor of this view.
I have a Validation class defined as:
class Validation: ObservableObject {
@Published var email = "" {
didSet {
self.validateEmail()
}
}
@Published var password = "" {
didSet {
self.validatePassword()
}
}
@Published var isValidEmail = false
@Published var isValidPassword = false
func validateEmail() {
// code
}
func validatePassword() {
// code
}
}
The Validation class is used in a view as such:
struct LoginView: View {
@EnvironmentObject var validation: Validation
var body: some View {
body(validation)
}
func body(_ validation: Validation) -> some View {
// SwiftUI code
}
}
The following test case crashes with the above error (I'm using Nimble/Quick instead of XCTest):
describe("LoginView") {
it("should prompt the user for their email") {
do {
let validation = Validation()
let loginView = LoginView()
let prompt = try loginView.inspect(validation).zStack().vStack(1).vStack(0).text(0).string()
expect(prompt).to(equal("What's your e-mail address?"))
} catch {
fail("failure - \(error)")
}
}
}
Is there something that I've missed that would be causing the crash? Any help on this is much appreciated.
Hi! Awesome work here, thank you!
Given this view and its test:
struct DetailView: View {
var body: some View {
EmptyView()
.navigationBarItems(trailing: Text("Done")) // commenting this makes the test run fine.
}
}
func testNavigationBarItems() {
expect {
// expected to not throw any error, got <notSupported("Please use \'navigationBarItems()\' for unwrapping the underlying view hierarchy.")>
try detailView.body.inspect().emptyView()
}.notTo(throwError())
expect {
// expected to not throw any error, got <modifierNotFound(parent: "DetailView", modifier: "navigationBarItems")>
try detailView.inspect().navigationBarItems().emptyView()
}.notTo(throwError())
}
Is this not supported, or am I missing something?
If I try to makeIterator() on VStack with 1 element
VStack {
Text("Some introduction")
}
iterator.next() will always have value, this creates a infinite loop
Do you have any thoughts on the amount of work required to query nested interfaces? More like how UI Testing works?
Meaning, not having to write out every single intermediate view to get to a deeply nested one. Right now a complex hierarchy takes a lot of boilerplate to verify deeply nested items. Take, for example, the following.
// ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NestedView(text: "First text.")
NestedView(text: "Second text.")
}
}
}
}
struct NestedView: View {
var text: String
var body: some View {
HStack {
Spacer()
Image(systemName: "plus.app.fill")
Text(text)
}
}
}
// ContentViewTests.swift
import XCTest
import ViewInspector
@testable import Ruka
class RukaTests: XCTestCase {
func testNestedInterfaces() throws {
let subject = ContentView()
let secondText = try subject.inspect()
.navigationView()
.vStack(0)
.view(NestedView.self, 1)
.hStack()
.text(2)
.string()
XCTAssertEqual(secondText, "Second text.")
}
}
extension ContentView: Inspectable {}
extension NestedView: Inspectable {}
Ideally, I could write something like the following, more UI Testing style.
let secondText = try subject.inspect().texts["Second text."]
XCTAssert(secondText.exists)
I'm assuming that if this is possible it would require quite a bit of dynamic/meta programming since everything is nicely typed. Do you have any thoughts or ideas on this?
Currently how Snapshot testing works, is that you run the tests on a recording mode. Where you know that the views are correct, and later on, we can assert against that source of truth. Would it be possible to do something similar with this library?
I was wondering if would be helpful to create a script that auto-generates tests for a given View.
I am imagining this script to be something like:
The benefits will be that we can have a tool that combines the idea of Snapshot testing but using unit tests.
Wdyt? Did you explore some idea like this before?
Some of the inspiration for this idea are this library of Android and this layout inspector.
Hi, I would like to use ViewInspector to test my views in Swift UI.
When I try to launch my test, 3 errors appears:
Can someone help me please ?
projectUITests.swift
import XCTest
import ViewInspector
@testable import project
extension ContentViewTest: Inspectable { }
final class ContentViewTests: XCTestCase {
func testStringValue() throws {
let sut = ContentViewTest()
let value = try sut.inspect().text().string()
XCTAssertEqual(value, "Hello, world!")
}
}
ContentViewTest.swift
import SwiftUI
struct ContentViewTest: View {
var body: some View {
Text("Hello, world!")
}
}
Hi
I'm trying out ViewInspector, which seems like a nice option for writing unittests for the views.
I had a few working tests for a view, until I decided to refactor a bit, and introduce a new View as my the root, which is a ViewBuilder.
Now I can't figure out how to extract it's child.
My "base viewbuilder" is defined like this
struct FormBaseView<Content: View>: View {
var body: some View {
content
.padding()
...a few more modifiers....
.onAppear {
.....
}
}
}
In my test - before wrapping the content in the FormBaseView
I had the following first line
let bodyContainer = try view.inspect().vStack()
Now, it's no longer a vStack at the root level, so this one gives a runtime error, which makes sense
caught error: "typeMismatch(factual: "FormBaseView<TupleView<(ModifiedContent<IDView<AnyView, ViewState>, _TraitWritingModifier<TransitionTraitKey>>, Spacer)>>", expected: "VStack")"
The problem is, that I haven't figured out the right solution for what to put instead of vStack to make it work. I have tried view(FormBaseView.Self)
but is uncertain what needs to be done for that to actually compile.
Can you point me in a direction for what is needed to be able to use my FormBaseView
?
We are thinking to start using library for new SwiftUI features, but we need Carthage support because we can not use Swift Package Manager yet. Would it be possible to support it?
Hello,
Is there any ways to test background color (or any other colors) of views (e.g. Text().background) ?
I have the following view, in which an optional Text
view is toggled in and out of visibility when the button is tapped:
struct MyView: View {
@State var showErrorMessage = false
var body: some View {
VStack {
Button(action: {
self.showErrorMessage.toggle()
print("showErrorMessage = \(self.showErrorMessage)")
}) {
Text("Click me.")
}
if showErrorMessage {
Text("ERROR")
.foregroundColor(Color.red)
}
}
}
}
I am trying to test the optional view with the following:
it("should show an error message when the button is pressed") {
do {
let view = MyView()
let button = try view.inspect().vStack().button(0)
try button.tap()
let errorMessage = try view.inspect().vStack().text(1).string()
expect(view.showErrorMessage).to(beTrue())
expect(errorMessage).to(equal("ERROR"))
} catch {
fail("failure - \(error)")
}
}
The test fails on both assertions, as the showErrorMessage
property remains false and the optional Text
view remains hidden. Additionally, showErrorMessage = false
is printed to the console when try button.tap()
is called.
When I run the code in the UI simulator, it works as expected; the Text
view is toggled in and out of visibility and the correct value of showErrorMessage
is printed to the console.
Is there something I'm missing that's causing the different behavior in the test versus the UI simulator?
Demo UIViewRepresentable
Code:
Just check https://github.com/SDWebImage/SDWebImageSwiftUI/blob/master/SDWebImageSwiftUI/Classes/AnimatedImage.swift :)
Demo Unit Test Code:
func testAnimatedImageBinding() throws {
let expectation = self.expectation(description: "AnimatedImage binding control")
var binding = Binding<Bool>(wrappedValue: true)
let imageView = AnimatedImage(name: "TestImage.gif", bundle: testImageBundle(), isAnimating: binding)
let introspectView = imageView.introspectAnimatedImage { animatedImageView in
if let animatedImage = animatedImageView.image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 5)
} else {
XCTFail("SDAnimatedImageView.image invalid")
}
XCTAssertTrue(animatedImageView.isAnimating)
binding.wrappedValue = false
binding.update()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
XCTAssertFalse(binding.wrappedValue)
XCTAssertFalse(imageView.isAnimating)
XCTAssertFalse(animatedImageView.isAnimating) // <----- Failed on this line
expectation.fulfill()
}
}
_ = try introspectView.inspect(AnimatedImage.self)
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
}
The library says watchOS is supported but when I try to build, either by opening as a package in Xcode (not the .xcodeproj file) or as a dependency in another project, I get failures due to the use of XCTest
which isn't supported.
/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator6.1.sdk/usr/lib/swift/XCTest.swiftmodule/i386.swiftinterface:6:19: XCTest is not available when building for watchOS Simulator. Consider using `#if targetEnvironment(macCatalyst)` to conditionally import this framework when building for Mac Catalyst.
./SourcePackages/checkouts/ViewInspector/Sources/ViewInspector/InspectableView.swift:2:8: Failed to load module 'XCTest'
Moved discussion.
I have a view with the following
struct FileLinkView<LinkView:View>: View {
var item:FBFile
let linkView:(URL)->LinkView
...
}
I cannot figure out how to inspect it live here is the test case but I get the error the "Generic parameter 'LinkView' cannot be inferred
func testListPreviewFile() {
let bundlePath = getBundleTestRoot()
let resourceFile:URL = bundlePath.appendingPathComponent("Baymax.jpg")
let item = FBFile(filePath: resourceFile, xInfo0: nil, xInfo1: nil)
let preview = PreviewController(url: resourceFile)
let view = FileLinkView(item: item) { file in
preview}
do {
try view.inspect().view(FileLinkView.self) //<- Inferred Error Occurs here
XCTAssert(false, "View did not throw error")
} catch {
}
}
Any help would be appreciated.
It appears that because the path for accessibilityLabel()
uses elements|some
, I always get a thrown error when trying to read it. In my inspection of the inspector (sorry!) I have the following modifier:
โฟ modifier : AccessibilityAttachmentModifier
โฟ attachment : Optional<AccessibilityAttachment>
โฟ some : AccessibilityAttachment
โฟ properties : AccessibilityProperties
โฟ plist : [TypedValueKey = Optional(SwiftUI.AccessibilityValue(value: nil, description: Optional(SwiftUI.Text(storage: SwiftUI.Text.Storage.verbatim("123456789012"), modifiers: [])))), LabelKey = Optional(SwiftUI.Text(storage: SwiftUI.Text.Storage.verbatim("UPCA product code"), modifiers: [])), TraitsKey = AccessibilityTraitStorage(mask: SwiftUI.AccessibilityTraitStorage.(unknown context at $7fff2c6c4d64).TraitSet(rawValue: 288), values: SwiftUI.AccessibilityTraitStorage.(unknown context at $7fff2c6c4d64).TraitSet(rawValue: 288))]
โฟ elements : Optional<Element>
โฟ some : TypedValueKey = Optional(SwiftUI.AccessibilityValue(value: nil, description: Optional(SwiftUI.Text(storage: SwiftUI.Text.Storage.verbatim("123456789012"), modifiers: []))))
As you can see, LabelKey
is in plist
, but does not appear in plist.elements.some
. Only my accessibilityValue()
is in elements. Since accessibilityValue()
is also in plist
, I think the appropriate fix is to simply chop off elements|some
in the path for accessibilityElement(_::::)
let's imagine we have a view like this,
struct BaseView: View {
private let shouldShowSecondLine: Bool
init(shouldShowSecondLine: Bool = true) {
self.shouldShowSecondLine = shouldShowSecondLine
}
var body: some View {
VStack {
Text("Hey there")
shouldShowSecondLine ? AnyView(Text("Showing second line"))
: AnyView(EmptyView())
}
}
}
struct ChildView: View {
var body: some View {
BaseView(shouldShowSecondLine: false)
}
}
How can we inspect the ChildView
?
Thanks...
I'm trying to access a custom view like
public struct CustomControl<T>: View where T: View {
public init() {}
public var body: some View {
... do something with the T
}
}
using
let _ = try? someView.view(CustomControl<Text>.self)
I have to provide a type Text, is there a way I can omit the type?
This one seems relatively easy but there is no support for Spacer
I just installed ViewInspector in my project (via SPD) ViewInspector 0.5.1. in XCode 12.1
struct PlayerRowView : View {
var player: Player
var body: some View {
HStack() {
Spacer()
Text("#\(player.number) ")
Text(player.name)
Spacer()
Text(player.position)
Spacer()
Text(player.atBat)
Spacer()
BallField()
.stroke(Color.blue, lineWidth: 1)
.frame(width: 10, height: 10)
Spacer()
}
}
}
my XCTestCase...
func testStringValue() throws {
let taylor = Player(name: "Taylor Swift", number: "17", position: "8", atBat: "BB")
let view = PlayerRowView(player: taylor)
let hStack = try view.inspect().hStack()
let value = try hStack.text(2).string()
XCTAssertEqual(value, "Taylor Swift")
}
caught error: Type mismatch: PlayerRowView is not HStack
What am I missing about parsing the view hierarchy?
I have the same issue
my structure is as follows
struct MyView: View {
@ObservedObject var data: Data = Data()
internal let inspection = Inspection() //for testing UI
var body: some View {
VStack() {
Spacer()
if data.isThis {
AnyView(SampleView(text: "test1", colour: .gray))
Spacer().frame(height: 10)
}
if data.isThis2 {
AnyView(SampleView(text: "test2", colour: .green))
Spacer().frame(height: 10)
}
if !data.isOnline {
AnyView(SampleView(text: "test3", colour: .red))
}
Spacer().frame(height: 55)
}.onReceive(inspection.notice) { self.inspection.visit(self, $0) } //for testing UI
}
}
so :let text = try view.vStack().anyView(3).view(SampleView.self).text().string()
works and text = "test 3"
however I can't access the other 2 SampleViews as they are Tuples
InspectableView
โฟ content : Content
โฟ view : VStack<TupleView<(Spacer, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional, ModifiedContent<Spacer, _FrameLayout>)>>
โฟ _tree : Tree<_VStackLayout, TupleView<(Spacer, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional, ModifiedContent<Spacer, _FrameLayout>)>>
โฟ root : _VStackLayout
โฟ alignment : HorizontalAlignment
โฟ key : AlignmentKey
- bits : 140735363993224
- spacing : nil
โฟ content : TupleView<(Spacer, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>, Optional, ModifiedContent<Spacer, _FrameLayout>)>
โฟ value : 5 elements
โฟ .0 : Spacer
- minLength : nil
โฟ .1 : Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>
โฟ some : TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>
โฟ value : 2 elements
โฟ .0 : AnyView
โฟ storage : <AnyViewStorage: 0x600003e29bc0>
โฟ .1 : ModifiedContent<Spacer, _FrameLayout>
โฟ content : Spacer
- minLength : nil
โฟ modifier : _FrameLayout
- width : nil
โฟ height : Optional
- some : 10.0
โฟ alignment : Alignment
โฟ horizontal : HorizontalAlignment
โฟ key : AlignmentKey
- bits : 140735363993224
โฟ vertical : VerticalAlignment
โฟ key : AlignmentKey
- bits : 140735363993201
โฟ .2 : Optional<TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>>
โฟ some : TupleView<(AnyView, ModifiedContent<Spacer, _FrameLayout>)>
โฟ value : 2 elements
โฟ .0 : AnyView
โฟ storage : <AnyViewStorage: 0x600003e296c0>
โฟ .1 : ModifiedContent<Spacer, _FrameLayout>
โฟ content : Spacer
- minLength : nil
โฟ modifier : _FrameLayout
- width : nil
โฟ height : Optional
- some : 10.0
โฟ alignment : Alignment
โฟ horizontal : HorizontalAlignment
โฟ key : AlignmentKey
- bits : 140735363993224
โฟ vertical : VerticalAlignment
โฟ key : AlignmentKey
- bits : 140735363993201
โฟ .3 : Optional
โฟ some : AnyView
โฟ storage : <AnyViewStorage: 0x600003e29740>
โฟ .4 : ModifiedContent<Spacer, _FrameLayout>
โฟ content : Spacer
- minLength : nil
โฟ modifier : _FrameLayout
- width : nil
โฟ height : Optional
- some : 55.0
โฟ alignment : Alignment
โฟ horizontal : HorizontalAlignment
โฟ key : AlignmentKey
- bits : 140735363993224
โฟ vertical : VerticalAlignment
โฟ key : AlignmentKey
- bits : 140735363993201
- modifiers : 0 elements
so how do I access the tuple at vStack index 1? and 2?
(lldb) po view.vStack().anyView(1)
โฟ Unable to extract AnyView: please specify its index inside parent view
notSupported : "Unable to extract AnyView: please specify its index inside parent view"
Hi, I have the following View structure:
VStack(alignment: .leading, spacing: 50) {
Text("What Organization is this Station for?")
ForEach(orgs) { org in
VStack {
NavigationLink(destination: BrandsView(),
tag: org.id ?? 0,
selection: self.$selectedOrgId) {
EmptyView()
}
Text(org.name)
.onTapGesture {
self.selectedOrgId = org.id
}
}
}
}
I have this pulled out of the outer view as a var
in a test case.
let topSection = try orgsView.inspect().zStack().vStack(1).hStack(0).vStack(0)
let value = try topSection.text(0).string()
expect(value).to(equal("What Organization is this Station for?"))
Now I want to inspect the ForEach
, but having trouble figuring out how...
let forEach = try topSection.forEach(1)
print(forEach)
This prints:
InspectableView<ForEach>(view: SwiftUI.ForEach<Swift.Array<PosModels.Organization>, Swift.Optional<Swift.Int64>, SwiftUI.VStack<SwiftUI.TupleView<(SwiftUI.NavigationLink<SwiftUI.EmptyView, Salido.BrandsView>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI.AddGestureModifier<SwiftUI._EndedGesture<SwiftUI.TapGesture>>>, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<SwiftUI.Color>>>, SwiftUI._AppearanceActionModifier>)>>>(data: [], content: (Function), idGenerator: SwiftUI.ForEach<Swift.Array<PosModels.Organization>, Swift.Optional<Swift.Int64>, SwiftUI.VStack<SwiftUI.TupleView<(SwiftUI.NavigationLink<SwiftUI.EmptyView, Salido.BrandsView>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI.AddGestureModifier<SwiftUI._EndedGesture<SwiftUI.TapGesture>>>, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<SwiftUI.Color>>>, SwiftUI._AppearanceActionModifier>)>>>.IDGenerator.keyPath(Swift.KeyPath<PosModels.Organization, Swift.Optional<Swift.Int64>>), contentID: 0), envObject: ViewInspector.Inspector.EnvironmentObjectNotSet())
๐ฎ
If I try to just do:
let obj2 = try forEach.anyView(0)
print(obj2)
It throws an error:
Printing description of error.viewIndexOutOfBounds:
โฟ 2 elements
- index : 0
- count : 0
What / how can I get at the inner contents of the forEach
. I took a look at your ForEach
tests, but it wasn't quite clear to me -- you have very simple struct
and text
. These are all complex objects with generics embedded, and I can't seem to index into the zero position with anyView
?
The following test fails:
func testRangeBased() throws {
let range = 0..<5
let view = ForEach(range) { Text(verbatim: "\($0)") }
let sut = try view.inspect().forEach()
XCTAssertEqual(sut.underestimatedCount, 5)
XCTAssertEqual(try sut.text(4).string(), "\(range.upperBound - 1)")
XCTAssertThrowsError(try sut.text(range.upperBound))
}
Consider this View.body
function:
var body: some View {
EmptyView()
.overlay(Text("overlaid").offset())
}
and the following test:
func testModifiedOverlayInspection() throws {
let text = "Abc"
let sut = try EmptyView().overlay(Text(text).offset())
.inspect().emptyView().overlay().text().string()
XCTAssertEqual(sut, text)
}
This fails (/<unknown>: failed: caught error: Type mismatch: ModifiedContent<Text, _OffsetEffect> is not Text
).
So I tried to ask for offset()
first but that also fails (/<unknown>: failed: caught error: ModifiedContent<Text, _OffsetEffect> does not have 'offset' modifier
)
Is it possible to make modifierAttribute as public so that we can extend it and add support for more modifiers? Unless there is a way to actually add support for different modifiers that are not available currently.
For example, for
Currently we have both .uiImage()
and .nsImage()
. However, due to some performance or bugs of SwiftUI, we have to use the CGImage based to provide the SwiftUI.Image
. I want to write test case to ensure this behavior.
Bugs: SDWebImage/SDWebImageSwiftUI#101
See the API: https://developer.apple.com/documentation/swiftui/image/3364311-init
So, please make this API public, or provide the correspond API like .cgImage()
I am experiencing strange issue.
Xcode hangs after I run the following "Test"
Version 12.0.1 (12A7300)
import SwiftUI
struct ContentView: View {
@StateObject var vm = ContentVM()
var body: some View {
VStack {
ForEach(vm.items, id: \.self) { title in
Button(title) {
vm.showText = title
}.padding(.vertical, 5)
}
}
}
}
class ContentVM: ObservableObject {
@Published var showText: String?
let items = ["A1", "A2", "A3"]
}
import XCTest
import ViewInspector
import SwiftUI
@testable import AnotherCheck
extension ContentView: Inspectable {}
class AnotherCheckTests: XCTestCase {
func testButton() throws {
let sut = ContentView()
let k = try sut.inspect().vStack()
}
}
I wanna test this view.
struct ContentView: View {
var body: some View {
NavigationView {
Text("test")
.navigationBarItems(trailing: EmptyView())
}
}
}
But this test caught error.
class ContentViewTests: XCTestCase {
func test() throws {
let sut = ContentView()
_ = try sut.inspect()
.navigationView()
.text(0)
}
}
extension ContentView: Inspectable {}
How can I inspect Text("test")
?
I would like to find out how to setInput(:)
for a UIViewRepresentable UITextField, if it's possible? Is there a way for my custom textfield to conform to your TextField ViewType so that input
and setInput
is exposed?
EDIT:
I am referencing the custom TextField in this way:
try view.geometryReader().hStack().view(DropdownUITextField.self, 1).textField()
I get this error: Fatal error: body() should not be called on DropdownUITextfield
There is no current support for shapes: Rectangle
, Circle
, Ellipse
, Capsule
, Path
, RoundedRectangle
.
There is no current support for manipulated shapes: TransformedShape
, InsettableShape
, OffsetShape
, RotatedShape
, ScaledShape
.
import XCTest
import ViewInspector
@testable import RandomListProject
extension ContentView: Inspectable { }
class ContentViewTests: XCTestCase {
func testStringValue() throws {
let sut = ContentView()
let value = try sut.inspect().text().string()
XCTAssertEqual(value, "Hello, world!")
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
above is my testcase class and content view. Still its showing me following error?
XCTAssertEqual failed: ("Optional("Hello, World!")") is not equal to ("Optional("Hello, world!")")
Hello @nalexn !
I'm trying test a custom view that contains a @ViewBuilder
implementation and a specific ActivityIndicator
in your body. The code is something like below:
struct ActivityIndicatorView<Content: View>: View {
@Binding
var isShowing: Bool
let content: Content
var body: some View {
// others parent views
...
// Show a loading icon
ActivityIndicator()
}
init(isShowing: Binding<Bool>, @ViewBuilder content: () -> Content) {
_isShowing = isShowing
self.content = content()
}
}
So, my CustomView
class use this ActivityIndicatorView
in your body. The others view are children of this activity view, like this:
struct ContentView: View {
@State
var showLoadingView: Bool = false
var body: some View {
ActivityIndicatorView(isShowing: .constant(showLoadingView)) {
VStack {
NavigationView {
}
}
}
}
}
So, how can I test this, to get the VStack
and NavigationView
from ContentView => ActivityIndicatorView
? I tried the code below in my test class:
import ViewInspector
// Add Inspectable to the two views
extension ContentView : Inspectable { }
extension ActivityIndicatorView : Inspectable { }
class MyClassTests: XCTestCase {
func testHeadless() throws {
let sut = ContentView()
// Tried this, but was that error with the Generic type in ActivityIndicatorView<>
let activityView = try sut.inspect().view(ActivityIndicatorView.self).anyView()
//Tried this, but I got:
// "caught error: "typeMismatch(factual:
//"ActivityIndicatorView<VStack<NavigationView<EnvironmentReaderView
//<ModifiedContent<ModifiedContent<ScrollView<TupleView<(SearchBar, ImagesRow)>>,
// _PreferenceWritingModifier<NavigationBarTitleKey>>,
//_PreferenceWritingModifier<NavigationBarItemsKey>>>>>>", expected: "AnyView")"
// let scrollView = try sut.inspect().anyView().vStack().navigationView(0).scrollView(0)
XCTAssertNotNil(scrollView)
}
}
How can I write in the code snippet above, I've always got errors like this:
caught error: "typeMismatch(factual: "ActivityIndicatorView<VStack<NavigationView<EnvironmentReaderView<ModifiedContent<ModifiedContent<ScrollView<TupleView<(SearchBar, ImagesRow)>>, _PreferenceWritingModifier<NavigationBarTitleKey>>, _PreferenceWritingModifier<NavigationBarItemsKey>>>>>>", expected: "AnyView")"
Can you help me?
Given the following example:
let text = Text("bold").bold() + Text(" ") + Text("italic").italic()
I would like to verify that whole "italic" word has correct attribute applied without the need to manually calculate correct range (5..<11
I believe). My first idea was to use range(of:)
method on string to find the part that interests me like this:
let inspectableText = try text.inspect().text()
let string = try inspectableText.string()
let attributes = try inspectableText.attributes()
let italicTextRange = string!.range(of: "italic")!
XCTAssertTrue(try attributes[italicTextRange].isItalic()) // ERROR: Cannot convert value of type 'Range<String.Index>' to expected argument type 'Range<Int>'
Unfortunately that is currently not possible due to marked error.
Do you think it would be desirable to extend TextAttributes
struct to allow such checks? I'll probably submit a proof of concept pull request if you agree. (Though I'm not yet sure if that's even possible ๐, EDIT: first attempt shows that it is possible)
Alternatively perhaps you know of another way to avoid counting letters?
Currently, it seems the only way to inspect font properties is by an actual instance of Font
as demonstrated in the guide.
extension InspectableView {
func find(textWithFont font: Font) throws -> InspectableView<ViewType.Text> {
return try find(ViewType.Text.self, where: {
try $0.attributes().font() == font
})
}
}
let text = try sut.find(textWithFont: .headline)
Imagine the design team you are working with has specified that all text in the app should use the Gotham typeface instead of the system font. One might use ViewInspector to automatically highlight all text that is not Gotham so that it stands out during testing. In order to do this today I would need to pass in multiple Font
instances for each possible variation (bold, italic, each font size etc...).
Is it possible to expose the font family name, point size or other properties that you would find on UIFont? Is there at least some string representation that can be pulled out (so that one could do a substring compare for the font name).
Hi there.
So I know there exists a way to inspect the view hierarchy for a particular UI component by specifying the view hierarchy explicitly, such as try view.inspect().vStack().button(0)
. However, to avoid exposing implementation details of the view hierarchy in my tests, I'd instead like to determine if there exists a UI component within a view hierarchy that matches some constraint.
For example, say there is a landing screen with a view hierarchy that contains a sign-in button along with other UI components. In my test, I'd like to verify that there exists a button with the text, "Sign In," rather than specifying the view hierarchy explicitly and thereby exposing implementation details.
Is there any way to do this? Much appreciated.
struct MapSwiftUIView: View {
@ObservedObject var viewModel: MapViewModel
var body: some View {
Map(coordinateRegion: self.$viewModel.region)
}
}
I'd like to verify the Map is showing the region I gave to the MapViewModel (the center lat/lng and span) in my unit test.
sut.inspect().Map() // no member 'Map'.
Are the new SwiftUI views of iOS 14 not supported yet?
I think this is very similar to #9 @nrivard had
I made a
func accessibility<S: StringProtocol>(label: S) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
return self.accessibility(label: Text(label))
}
func accessibility<S: StringProtocol>(hint: S) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
return self.accessibility(hint: Text(hint))
}
func accessibility<S: StringProtocol>(value: S) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
return self.accessibility(value: Text(value))
}
}
Then in my test
let button = Button(label: { Text("Some Text") }, action: {})
.accessibility(label: "Like")
.accessibility(hint: "Tap will perform like to content")
will fail inspect whichever on the second
First off, amazing work on this library! I'm looking forward to diving in and testing a few of my apps.
I'm trying to work with a view that uses @EnvironmentObject
but can't seem to get it to work, even after reading through guide.md
.
I receive the following error on the Text(strings.all.first ?? "(none)")
line:
Thread 1: Fatal error: No ObservableObject of type Strings found. A View.environmentObject(_:) for Strings may be missing as an ancestor of this view.
Here's the smallest amount of code I can write to reproduce the error.
import XCTest
import ViewInspector
extension StringsView: Inspectable { }
class ViewTests: XCTestCase {
func testEnvironmentObject() throws {
let subject = StringsView()
let strings = Strings(["first", "second"])
ViewHosting.host(view: subject.environmentObject(strings))
let text = try subject.inspect().text().string()
XCTAssertEqual(text, "first")
}
}
import SwiftUI
class Strings: ObservableObject {
var all: [String] = []
init(_ strings: [String]) {
all = strings
}
}
struct StringsView: View {
@EnvironmentObject var strings: Strings
var body: some View {
Text(strings.all.first ?? "(none)")
}
}
I'm assuming I'm doing something wrong with either the order of operations or "losing" the reference to the hosting view but I can't tell for sure. Any help would be greatly appreciated!
Is it not possible to test a nested @EnvironmentObject
? The following view works when running in the simulator.
import SwiftUI
struct ParentView: View {
@EnvironmentObject var strings: Strings
internal let inspection = Inspection<Self>()
var body: some View {
NestedView()
.environmentObject(strings)
.onReceive(inspection.notice) { self.inspection.visit(self, $0) }
}
}
struct NestedView: View {
@EnvironmentObject var strings: Strings
internal let inspection = Inspection<Self>()
var body: some View {
Text(strings.all[0])
.onReceive(inspection.notice) { self.inspection.visit(self, $0) }
}
}
class Strings: ObservableObject {
@Published var all: [String] = []
}
let strings = Strings()
strings.all.append("New string.")
let contentView = ParentView()
.environmentObject(strings)
But the following test fails.
Thread 1: Fatal error: No ObservableObject of type Strings found. A View.environmentObject(_:) for Strings may be missing as an ancestor of this view.
import ViewInspector
@testable import App
import XCTest
class Tests: XCTestCase {
func testNestedEnvironmentObject() throws {
let strings = Strings()
strings.all.append("First string")
let view = ParentView()
let expectation = view.inspection.inspect { view in
let text = try view.view(NestedView.self, 0).text().string()
XCTAssertEqual(text, "First string")
}
let hostedView = view.environmentObject(strings)
ViewHosting.host(view: hostedView)
wait(for: [expectation], timeout: 0.1)
}
}
extension ParentView: Inspectable {}
extension NestedView: Inspectable {}
After I'd added ViewInspector, Live Preview on Device stopped working, it seems:
linker command failed with exit code 1 (use -v to see invocation)
Build target ViewInspector:
note: Set ENABLE_PREVIEWS=NO because SWIFT_OPTIMIZATION_LEVEL=-O, expected -Onone (in target 'ViewInspector' from project 'Pods')
Link ViewInspector (arm64):
ld: '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework/XCTest' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework/XCTest' for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
The regular Live Preview is working fine though
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.