Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inspection Structure Fails with Xcode 16 #327

Open
Mordil opened this issue Aug 23, 2024 · 18 comments
Open

Inspection Structure Fails with Xcode 16 #327

Mordil opened this issue Aug 23, 2024 · 18 comments

Comments

@Mordil
Copy link

Mordil commented Aug 23, 2024

Running the Xcode 16 Beta 6, and the 0.10.0 branch of ViewInspector

View queries that worked in Xcode 15 & 0.9.x release no longer work.

let view = MySwiftUIView()
let shape = try view.inspect().hStack().shape(0)
// error - hStack() found MyModule.MySwiftUIView instead of HStack
@nalexn
Copy link
Owner

nalexn commented Aug 25, 2024

Hey, this sounds like a known issue with swift 6 compiler - it started inserting AnyView at random, which changes absolute paths to target views. It's still unclear if this will get released like that, but I highly recommend refactoring your tests to use find instead of absolute paths

@Mordil
Copy link
Author

Mordil commented Aug 25, 2024

Doesn’t this render much of the API useless? How would I replace this with the find API?

I tried find(ViewType.HStack.self).shape(0) but got a similar error

@Mordil
Copy link
Author

Mordil commented Aug 25, 2024

Ah. As I followed the threads you linked, I now understand.

I appreciate all the time you’ve spent researching and trying to fix this. I respect the situation you’re in from the changes from Apple

@A30008910-wei
Copy link

A30008910-wei commented Sep 6, 2024

I had to add .anyView() after .inspect() in most of cases, I would wait for the stable version of Xcode 16 to recheck my fixes.

@nalexn
Copy link
Owner

nalexn commented Sep 6, 2024

@A30008910-wei I haven't updated the docs yet, but there is a new special call .implicitAnyView() exactly for this. It gives better clarity in the tests, giving insight that it's not your AnyView you should expect in your view, and also this call is compiler aware, where it'd add attempt to unwrap AnyView for swift 6 compiler, and do nothing for swift 5 compiler

@nh7a
Copy link
Contributor

nh7a commented Sep 6, 2024

@nalexn since it depends on the SDK version, not the compiler version, the check should be #if canImport(Swift, _version: 6.0), instead.

@nh7a
Copy link
Contributor

nh7a commented Sep 6, 2024

BTW, I have a view that requires to go with try view.inspect().anyView().anyView().anyView().anyView().vStack() while it is fine with view.inspect().vStack() on Xcode 15. So, I feel I just have to use find() regardless.

@nh7a
Copy link
Contributor

nh7a commented Sep 10, 2024

@nalexn we need MultipleViewContent version of .implicitAnyView(), too.

@nalexn
Copy link
Owner

nalexn commented Sep 10, 2024

@nh7a the AnyView has been updated to conform to MultipleViewContent, you should be able to do .implicitAnyView().someView(1). If this doesn't help - please let me know, and provide more context with an example


I realized implicitAnyView returns untyped view, so that trick won't work. I guess use .anyView() in these cases, that is .anyView().someView(1)

@nh7a
Copy link
Contributor

nh7a commented Sep 10, 2024

@nalexn this is my situation:

let sut = try view.inspect()
let hStack = try sut.hStack(4)

#if canImport(Swift, _version: 6.0)
  try hStack.anyView(0).divider(0)  // works for 16.0
#else
  try hStack.divider(0)  // works for Xcode 15.4
#endif

The below doesn't compile:

try hStack.anyView().divider(0)  // Referencing instance method 'anyView()' on 'InspectableView' requires that 'ViewType.VStack' conform to 'SingleViewContent'

try hStack.implicitAnyView().divider(0)  // Referencing instance method 'implicitAnyView()' on 'InspectableView' requires that 'ViewType.VStack' conform to 'SingleViewContent'

To support both Xcode 15.4 & 16.0, I think we want to be able to write:

try hStack.implicitAnyView(0).divider(0)

And the below works fine (as I confirmed with my local branch... was what I thought but maybe not; I should make it sure later):

public extension InspectableView where View: MultipleViewContent {   
#if canImport(Swift, _version: 6.0)
    func implicitAnyView(_ index: Int) throws -> InspectableView<ViewType.AnyView> {
        anyView(index)
    }
#else
    func implicitAnyView(_ index: Int) throws -> InspectableView<View> {
        self
    }
#endif
}

@Rob-2024
Copy link

I am seeing the same problem with ButtonStyle tests. The .inspect(isPressed:) function returns an AnyView which breaks all my tests. I have tried the suggestions in this issue without any success. At this point I was forced to "comment out" all my ButtonStyle tests so I was able to prepare for the iOS 18 release. Here is an example that shows what's happening.

func testStyle() throws {
    struct TestStyle: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.label.background( configuration.isPressed ? Color.red : Color.blue)
            configuration.label.font(.headline)
        }
    }
    let test = TestStyle()
    let sut = try test.inspect(isPressed: false)
    XCTAssertEqual(try sut.background().color().value(), Color.blue)
    XCTAssertEqual(try sut.font(), Font.headline)
}

@nh7a
Copy link
Contributor

nh7a commented Sep 17, 2024

@Rob-2024 I'm not totally sure if it's worth keeping up in this way, but the code below can test and pass, at least. I hope there's a way to absorb this by ViewInspector itself.

func testStyle() throws {
    struct TestStyle: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.label.background( configuration.isPressed ? Color.red : Color.blue)
            configuration.label.font(.headline)
        }
    }
    let test = TestStyle()
    let sut = try test.inspect(isPressed: false)
    XCTAssertEqual(try sut.anyView().styleConfigurationLabel(0).background().color().value(), Color.blue)
    XCTAssertEqual(try sut.anyView().styleConfigurationLabel(1).font(), Font.headline)
}

@FelipeUgarte
Copy link

FelipeUgarte commented Sep 17, 2024

I have a similar problem: group() found AnyView instead of Group. Any suggestion?
Im using ViewInspector v0.10.0
Xcode 16
Build for swift 5

This is my code:

    @MainActor
    func testTruncatingReview_NoManagerResponse() throws {
        let review = Review(reviewDate: nil, firstName: nil, lastInitial: nil, review: "bla bla bla", overall: 4, response: nil, unitId: nil, reservationId: nil)
        let reviewView = GuestReviewView(review: review)
        let truncationExpectation = reviewView.inspection.inspect(after: 0.1) { view in // HERE IS THE ERROR
            XCTAssertTrue(try view.actualView().isTextTruncated)
            XCTAssertTrue(try view.actualView().isViewCollapsed)
            try view.find(button: .VCShared.SeeMoreButton).tap()
            XCTAssertFalse(try view.actualView().isViewCollapsed)
            XCTAssertThrowsError(try view.group().vStack(0).vStack(0).find(text: responseText))
            try view.group().vStack(0).find(button: .Shared.SeeLessButton).tap()
            XCTAssertThrowsError(try view.group().vStack(0).vStack(1).text(1))
            XCTAssertThrowsError(try view.group().vStack(0).find(button: Shared.SeeLessButton))
        }
        ViewHosting.host(view: reviewView)
        wait(for: [truncationExpectation], timeout: 1)
        let reviewLabel = try reviewView.inspect().group().vStack(0).view(TruncatedTextView.self, 0).vStack().text(0)
        XCTAssertEqual(try reviewLabel.string(), review.review)
    }

@christiancabarrocas
Copy link

I have exactly the same problem and I was wondering if there's any plan to upgrade the library to fix it as we have more than 5000 tests and it's a hard work to update it all with .anyView() or .implicitAnyView()

Thanks for your work @nalexn !

@KatherineInCode
Copy link

Per this Mastodon toot, it appears that a number of new AnyView objects have been added to the hierarchy. If you set SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO in your build settings, it will revert to the old behavior. Alternatively, I've had success by adding additional .anyView() methods in my calls, though I've had to do trial and error to find the right number of them.

@Weizzz
Copy link

Weizzz commented Oct 30, 2024

any way to add SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO to swift packages?

@nalexn
Copy link
Owner

nalexn commented Nov 2, 2024

Ok, just tried @KatherineInCode 's finding around SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO - it works!
It has to be added to the main project's target settings, as it controls how the main target compiles, an external library used for testing cannot control that. It's not obvious where to add that setting, so I figured it magically works when defined as "User-defined" value (which makes it truly a hidden undocumented feature):

_erasure_

I wish I could make func implicitAnyView() to respect this setting but the only way I could find how to read it is through Info.plist, which isn't as straightforward as I would expect. Maybe it's easier to either use implicitAnyView() everywhere or go with SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO and forget about this Xcode nonsense

@nh7a
Copy link
Contributor

nh7a commented Nov 4, 2024

This is the explanation of those AnyView. My question is, if SWIFT_ENABLE_OPAQUE_TYPE_ERASURE is official and reliable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants