Giter VIP home page Giter VIP logo

viewinspector's Introduction

whoami

I'm Alexey. A cat person living with a dog ๐Ÿคท๐Ÿผโ€โ™‚๏ธ

M.S. CS, developing for Apple platforms since 2011.

Obsessed with superior software design, clean code, reactive data-driven architectures, and team productivity boosters.

I'm reasoning about tricky tech topics on my blog, creating open-source dev tools and sample projects.

viewinspector's People

Contributors

alexbasson avatar andy-vanwagoner-jn avatar arclite avatar babbage avatar bachand avatar barnard-b avatar bryankeller avatar dreampiggy avatar dscyrescotti avatar fraur avatar joemasilotti avatar jonreid avatar karllas avatar lodstar avatar nalexn avatar nh7a avatar nrivard avatar patrickbdev avatar pobengtsson avatar rhwood avatar richard-gist avatar rivera-ernesto avatar somerandomiosdev avatar sureshjoshi avatar tobywoollaston avatar tunous avatar twostraws avatar tyler-keith-thompson avatar vruzeda avatar zooxop avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

viewinspector's Issues

forEach on forEach, index out of bounds

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)
}

Updating and accessing @State properties

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?

How can I inspect a Map?

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?

Verifying Text attributes based on Range<String.Index>

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?

Accessing non-optional content from a container containing other optional content

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.

How to inspect ForEach?

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?

How to inspect a view that takes a closure

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.

Loop when inspect 1 item in VStack

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

No support for Shapes

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.

Adding modifiers to overlay content breaks querying

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)

[Import error] No such module 'ViewInspector'

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:

viewinspector-import-error

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?

Error while running app

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.

Doesn't compile for watchOS

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'

Some way to inspect tuple view children

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.

How to work with @EnvironmentObject?

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!

Feature Request: Make modifierAttribute public

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

  1. Texts, there is no function to return the Font or Color of the Text.
  2. For Stacks, no function to return Spacing or Alignment.

More granular font introspection

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).

Inspect Views which overrides properties on other View.

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...

Carthage support

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?

Could not install on Xcode 12.2 beta

I am on Xcode 12.2 beta and I could not install the package. After following the steps described in the documentation the "Finish" button did not become highlighted. Is this a known issue?

Screenshot 2020-10-07 at 16 47 19
Thanks

Tuple issue

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"

[CustomView] View is not found

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?

Dynamic query not finding view when there is conditional content

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.

Can't inspect `accessibilityLabel()`

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(_::::)

Missing support for inspecting concatenated Text views

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

Script to autogenerates tests (like Snapshot testing)

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:

  1. Extract all the properties of a view, like color, frame, fonts, etc.
  2. Generate one test case for each property.
  3. Probably some manual work is need it to customize these tests, by adding more, or being more specific, etc.

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.

Type mismatch: PlayerRowView is not HStack || but it is.

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?

How can I test to present modal?

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()
                 }
            }
        }
    }
}

Issue when inspecting views that interact with navigationBarItems

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?

When using in `UIViewRepresentable`, update the `Binding` value does not trigger `updateUIView(:)` during unit test

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)
}

Nested EnvironmentObject

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 {}

Query nested interfaces

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?

Inspecting Custom UIViewRepresentable UITextField

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

Xcode hangs on same cases.

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()
    }

}

Accessing a custom view with generic type

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?

Is there a way to identify a view of any type in a SwiftUI view hierarchy given some constraint?

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.

Can't inspect `Accessibility Hint`

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

Since Xcode12 GeometryProxy uses more bytes in memory

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)
    }
}

Undefined symbol: nominal type descriptor for project.ContentViewTest

Hi, I would like to use ViewInspector to test my views in Swift UI.

When I try to launch my test, 3 errors appears:

  • Undefined symbol: nominal type descriptor for project.ContentViewTest
  • Undefined symbol: protocol conformance descriptor for project.ContentViewTest : SwiftUI.View in project
  • Undefined symbol: type metadata for project.ContentViewTest

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!")
    }
}

How to inspect views in NavigationView when it has navigationBarItems?

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")?

How I can inspect @EnvironmentObject

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?

Can't inspect Range<Int> based ForEach

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))
    }

Not working though both values are same

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!")")

Custom view(builder) example

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?

Fatal error: No ObservableObject of type [type] found.

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.

Live Preview on Device not working

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.