Skip to content

Conversation

@vince4
Copy link

@vince4 vince4 commented Nov 28, 2025

Short description 📝

The generated tests does not support #Preview with multiple views. Fix issue #146

#Preview
{
    NXBadgeView(item: NXBadgeViewItem(icon: "generic_small_star_10", text: "4.0", color: .pink))
    NXBadgeView(item: NXBadgeViewItem(icon: nil, text: "Top award", color: .purple))
    NXBadgeView(item: NXBadgeViewItem(icon: nil, text: "Top award", color: .yellow, isLarge: true))
}

^ This preview generate a test that doesn't build.

Solution 📦

Because #Preview is of type @ViewBuilder, the preview variables, used in the generated tests, should also be @ViewBuilder.

Test 🧪

I did not find any existing test for the PreviewTestsTemplate, but it would be nice to add some. @BarredEwe : Do you have some suggestions of tests I could add ?

@vince4 vince4 changed the title #146 Add ViewBuilder capacity to preview variables in the test template Add ViewBuilder capacity to preview variables in the test template Nov 28, 2025
@BarredEwe
Copy link
Owner

Hi! Thanks for the fix. Regarding tests, I think what we have in PrefireExample is enough here.

@vince4
Copy link
Author

vince4 commented Dec 2, 2025

Hi! Thanks for the fix. Regarding tests, I think what we have in PrefireExample is enough here.

I see. So I can add a new Preview inside PrefireExample that contains multiple views.
I will add one here : https://github.com/BarredEwe/Prefire/blob/main/Example/Shared/Examples/TestView.swift#L89-L94

@vince4 vince4 force-pushed the fix_preview_with_multiple_views branch from d5b3e1e to 7c14fde Compare December 2, 2025 21:15
@vince4
Copy link
Author

vince4 commented Dec 2, 2025

I added the Preview macro with multiple views to the Example project, but then I realized that my initial implementation was too naive. It broke support for UIView and UIViewController.

The current solution still isn’t perfect, because it’s technically possible to build a Preview with a single UIViewController without using a return statement. This works only because there is a specific (but private) result builder for them, called PreviewMacroBodyBuilder:

@available(iOS 17.0, macOS 14.0, tvOS 17.0, *)
@freestanding(declaration)
public macro Preview(
    _ name: String? = nil,
    traits: PreviewTrait<Preview.ViewTraits>...,
    @PreviewMacroBodyBuilder<UIViewController> body: @escaping @MainActor () -> UIViewController
) = #externalMacro(module: "PreviewsMacros", type: "KitViewMacro")

Initially, I tried to create my own result builder that supports both SwiftUI.View and UIViewController, but it didn’t work in the example. As soon as the closure includes a return statement, it disables the effect of the result builder.

So the current implementation works correctly for SwiftUI views using @ViewBuilder Previews, but it has a known limitation: this type of preview is not supported:

#Preview {
    UIViewController()
}

@BarredEwe
Copy link
Owner

A working solution, but I think we can make it a bit simpler using generic functions.

public init(@ViewBuilder _ view: @escaping @MainActor () -> Content, name: String, isScreen: Bool, device: DeviceConfig, traits: UITraitCollection = .init()) {

I quickly sketched out an implementation here - take a look. If you like it, we can merge it.

Prefire-15-26-16.patch

@vince4 vince4 force-pushed the fix_preview_with_multiple_views branch from 7c14fde to 2a7e62b Compare December 5, 2025 11:30
@vince4
Copy link
Author

vince4 commented Dec 5, 2025

@BarredEwe : Your solution is much nicer, directly modifying the PrefireSnapshot init. However, it doesn't compile in my project, because in some strange cases it still needs the @_disfavoredOverload to avoid this error:

TestViewTests.generated.swift:112:31 Ambiguous use of 'init(_:name:isScreen:device:traits:)'
TestViewTests.generated.swift:128:26 Generic parameter 'Content' could not be inferred

I managed to reproduce the error with this #Preview example:

#Preview(traits: .sizeThatFitsLayout) {
    let userStory: PreviewModel.UserStory = .testStory
    VStack
    {
        TestView(isLoading: true)
            .previewUserStory(userStory)
            .snapshot(delay: 0.1, precision: 0.9)
            .previewUserStory(.auth)
    }.padding()
}

I’ve updated the merge request.

@BarredEwe
Copy link
Owner

Great! I’ve checked it, it works well. Thanks for contributing!

@BarredEwe BarredEwe merged commit 41d9d23 into BarredEwe:main Dec 9, 2025
3 checks passed
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

Successfully merging this pull request may close these issues.

2 participants