Contents

dafurman/AddPreviews

A Swift macro that makes preview-based snapshot testing easier

Usage

Use in Previews

Just import AddPreviews and attach @AddPreviews to your preview struct.

import AddPreviews

@AddPreviews
struct MyView_Previews: PreviewProvider {
    static var stateOne: some View { MyView(state: .one) }
    static var stateTwo: some View { MyView(state: .two) }
}

This will generate a previews property containing each of your view states, along with display names to easily identify which one you're looking at in Xcode:

// (Generated)
static var previews: some View {
    stateOne.previewDisplayName("stateOne")
    stateTwo.previewDisplayName("stateTwo")
}

Use in Snapshot Tests

The real magic comes in the boilerplate removal in snapshot tests.

@AddPreviews makes an annotated preview provider iterable over each of its view properties, allowing a snapshot test to be reduced from this:

import SnapshotTesting
import XCTest

final class MyViewTests: XCTestCase {
    func testStateOne() {
        assertSnapshot(of: MyView_Previews.stateOne, as: .image(layout: .device(config: .yourDevice)))
    }
    
    func testStateTwo() {
        assertSnapshot(of: MyView_Previews.stateTwo, as: .image(layout: .device(config: .yourDevice)))
    }
}

To this - code that effortlessly scales with the addition of new preview states:

import SnapshotTesting
import XCTest

final class MyViewTests: XCTestCase {
    func testPreviews() {
        for preview in MyView_Previews() {
            assertSnapshot(of: preview, as: .image(layout: .device(config: .yourDevice)), named: preview.name)
        }
    }
}

All you have to do is rerecord snapshots when making an addition or change, or remove unused reference images when removing a preview state.

Motivation

Why create specific view properties? Why not just inline different states in the preview property itself?

This pattern makes writing snapshot tests for device-sized views a breeze, as shown above!

That said, this pattern is best-tailored for screen-sized views that should have their own reference images. For smaller views, this approach is overkill, and a preview comprising of multiple states in a stack could just be simply written and snapshotted directly, as shown below:

Preview:

struct RowView_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            RowView(title: "Title")
            RowView(title: "Title", subtitle: "Subtitle")
            RowView(title: "Title", subtitle: "Subtitle") {
                Image(systemSymbol: .envelopeFill)
            }
        }
    }
}

Snapshot:

final class RowViewTests: XCTestCase {
    func testPreviews() {
        assertSnapshot(of: RowView_Previews.previews, as: .image(layout: .device(config: .yourDevice)))
    }
}

What about the `#Preview` macro?

#Preview) is nice, concise, and is positioned as the future of Xcode previews, but it doesn't support snapshot testing in the way that PreviewProvider does, as shown above.

Using #Preview generates a struct with a mangled type name like this: $s17<YourTarget>33_5594AE1E7369B73F633885FC4E970BA7Ll7PreviewfMf_15PreviewRegistryfMu_

While it's technically possible to reference this type name (though ill-advised), there's still no View that can be extracted out from it that can be plugged into a snapshot test. DeveloperToolsSupport is currently a black-box in regards to how Xcode turns these previews into views.

License

This library is released under the MIT license. See LICENSE for details.

Package Metadata

Repository: dafurman/AddPreviews

Stars: 1

Forks: 0

Open issues: 0

Default branch: main

Primary language: swift

License: MIT

Topics: generated, macros, previews, snapshots, swift, tests

README: README.md