Skip to content

Commit

Permalink
New preview tests (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahsmartin authored Aug 12, 2024
1 parent 60335c6 commit 17b2be4
Show file tree
Hide file tree
Showing 13 changed files with 524 additions and 32 deletions.
13 changes: 13 additions & 0 deletions DemoApp/Demo Watch AppTests/DemoWatchPreviewTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// DemoWatchPreviewTest.swift
// Demo Watch AppTests
//
// Created by Noah Martin on 8/10/24.
//

import XCTest
import SnapshottingTests

final class DemoWatchPreviewTest: PreviewLayoutTest {

}
13 changes: 13 additions & 0 deletions DemoApp/Demo Watch AppTests/DemoWatchSnapshotTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// DemoWatchSnapshotTest.swift
// Demo Watch AppTests
//
// Created by Noah Martin on 8/10/24.
//

import Foundation
import SnapshottingTests

final class DemoWatchSnapshotTest: SnapshotTest {

}
282 changes: 274 additions & 8 deletions DemoApp/DemoApp.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions DemoApp/DemoAppTests/DemoAppPreviewTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// DemoAppPreviewTest.swift
// DemoAppTests
//
// Created by Noah Martin on 8/9/24.
//

import XCTest
import SnapshottingTests

final class DemoAppPreviewTest: PreviewLayoutTest {
override func snapshotPreviews() -> [String]? {
return nil
}

override func excludedSnapshotPreviews() -> [String]? {
return nil
}
}
19 changes: 19 additions & 0 deletions DemoApp/DemoAppTests/DemoAppSnapshotTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// DemoAppSnapshotTest.swift
// DemoAppTests
//
// Created by Noah Martin on 8/10/24.
//

import Foundation
import SnapshottingTests

class DemoAppSnapshotTest: SnapshotTest {
override func snapshotPreviews() -> [String]? {
return nil
}

override func excludedSnapshotPreviews() -> [String]? {
return nil
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// MultipleTests.swift
// DemoAppAccessibilityPreviewTest.swift
// DemoAppUITests
//
// Created by Noah Martin on 7/14/23.
Expand All @@ -9,7 +9,8 @@ import Foundation
import XCTest
import SnapshottingTests

class MyPreviewTest: PreviewTest {
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
class DemoAppAccessibilityPreviewTest: AccessibilityPreviewTest {

override func snapshotPreviews() -> [String]? {
return nil
Expand All @@ -19,10 +20,6 @@ class MyPreviewTest: PreviewTest {
return nil
}

override var enableAccessibilityAudit: Bool {
true
}

@available(iOS 17.0, *)
override func auditType() -> XCUIAccessibilityAuditType {
return .all
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Demo.swift
// DemoWatchAccessibilityPreviewTest.swift
// Demo
//
// Created by Noah Martin on 7/5/24.
Expand All @@ -9,7 +9,7 @@ import Snapshotting
import SnapshottingTests
import XCTest

final class Demo: PreviewTest {
final class DemoWatchAccessibilityPreviewTest: AccessibilityPreviewTest {

override func getApp() -> XCUIApplication {
return XCUIApplication()
Expand Down
4 changes: 2 additions & 2 deletions Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ public enum FindPreviews {

@MainActor
public static func findPreviews(included: [String]?, excluded: [String]?) -> [PreviewType] {
var previewsSet = included.map { Set($0) }
var excludedPreviewsSet = excluded.map { Set($0) }
let previewsSet = included.map { Set($0) }
let excludedPreviewsSet = excluded.map { Set($0) }

let previewTypes = findPreviews { name, proto in
guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) else { return true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// PreviewTest.swift
//
// AccessibilityPreviewTest.swift
//
//
// Created by Noah Martin on 8/9/24.
//
Expand All @@ -9,7 +9,9 @@ import Foundation
import SnapshottingTestsObjc
import MachO

open class PreviewTest: EMGPreviewBaseTest {
// This is an XCUITest that uses XCUIApplication.performAccessibilityAudit to test previews
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
open class AccessibilityPreviewTest: EMGPreviewBaseTest {

open func getApp() -> XCUIApplication {
XCUIApplication()
Expand All @@ -26,10 +28,6 @@ open class PreviewTest: EMGPreviewBaseTest {
nil
}

open var enableAccessibilityAudit: Bool {
true
}

@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
open func auditType() -> XCUIAccessibilityAuditType { .all }

Expand Down Expand Up @@ -159,13 +157,9 @@ open class PreviewTest: EMGPreviewBaseTest {
XCTFail("Failed to parse JSON: \(error)")
}

if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
if enableAccessibilityAudit {
let app = getApp()
try? app.performAccessibilityAudit(for: auditType()) { [weak self] issue in
return self?.handle(issue: issue) ?? false
}
}
let app = getApp()
try? app.performAccessibilityAudit(for: auditType()) { [weak self] issue in
return self?.handle(issue: issue) ?? false
}
}
}
28 changes: 28 additions & 0 deletions Sources/SnapshottingTests/EMGPreviewBaseTest+PreviewFilters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// EMGPreviewBaseTest+PreviewFilters.swift
//
//
// Created by Noah Martin on 8/9/24.
//

import Foundation
import SnapshotPreviewsCore
import SnapshottingTestsObjc

extension PreviewFilters where Self: EMGPreviewBaseTest {
@MainActor
static func matchingPreviewTypes() -> [PreviewType] {
let instance = self.create()
return FindPreviews.findPreviews(included: instance.snapshotPreviews(), excluded: instance.excludedSnapshotPreviews())
}
}

extension EMGDiscoveredPreview {
static func from(previewType: PreviewType) -> EMGDiscoveredPreview {
let d = EMGDiscoveredPreview()
d.typeName = previewType.typeName
d.displayName = previewType.displayName
d.numberOfPreviews = NSNumber(value: previewType.previews.count)
return d
}
}
17 changes: 17 additions & 0 deletions Sources/SnapshottingTests/PreviewFilters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// PreviewFilters.swift
//
//
// Created by Noah Martin on 8/9/24.
//

import Foundation

public protocol PreviewFilters {
// Override to return a list of previews that should be snapshotted.
// The default is null, which snapshots all previews.
// Elements should be the type name of the preview, like "MyModule.MyView_Previews"
func snapshotPreviews() -> [String]?

func excludedSnapshotPreviews() -> [String]?
}
54 changes: 54 additions & 0 deletions Sources/SnapshottingTests/PreviewLayoutTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// PreviewLayoutTest.swift
//
//
// Created by Noah Martin on 8/9/24.
//

import Foundation
import SnapshotPreviewsCore
import SnapshottingTestsObjc
import SwiftUI

// Test Xcode previews by forcing a layout pass of each one
open class PreviewLayoutTest: EMGPreviewBaseTest, PreviewFilters {

open func snapshotPreviews() -> [String]? {
nil
}

open func excludedSnapshotPreviews() -> [String]? {
nil
}

static private var previews: [PreviewType] = []

@MainActor
open override class func discoverPreviews() -> [EMGDiscoveredPreview] {
previews = matchingPreviewTypes()
return previews.map { EMGDiscoveredPreview.from(previewType: $0) }
}

@MainActor
open override func test(_ preview: EMGPreview) {
let previewType = Self.previews.first { $0.typeName == preview.preview.typeName }
guard let preview = previewType?.previews[preview.index.intValue] else {
XCTFail("Preview not found")
return
}

#if canImport(UIKit) && !os(watchOS)
let hostingVC = UIHostingController(rootView: AnyView(preview.view()))
#if os(visionOS) || os(watchOS)
hostingVC.view.sizeThatFits(CGSize(width: 100, height: CGFloat.greatestFiniteMagnitude))
#else
hostingVC.view.sizeThatFits(UIScreen.main.bounds.size)
#endif
#elseif canImport(AppKit)
let hostingVC = NSHostingController(rootView: AnyView(preview.view()))
_ = hostingVC.sizeThatFits(in: NSScreen.main!.frame.size)
#else
_ = ImageRenderer(content: AnyView(preview.view())).uiImage
#endif
}
}
72 changes: 72 additions & 0 deletions Sources/SnapshottingTests/SnapshotTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// SnapshotTest.swift
//
//
// Created by Noah Martin on 8/9/24.
//

import Foundation
import SnapshotPreviewsCore
import SnapshottingTestsObjc

// Generate snapshots of Xcode previews
open class SnapshotTest: EMGPreviewBaseTest, PreviewFilters {

open func snapshotPreviews() -> [String]? {
nil
}

open func excludedSnapshotPreviews() -> [String]? {
nil
}

private static func getRenderingStrategy() -> RenderingStrategy {
#if canImport(UIKit) && !os(watchOS) && !os(visionOS) && !os(tvOS)
return UIKitRenderingStrategy()
#else
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *) {
SwiftUIRenderingStrategy()
} else {
preconditionFailure("Cannot snapshot on this device/os")
}
#endif
}
private let renderingStrategy = getRenderingStrategy()

static private var previews: [SnapshotPreviewsCore.PreviewType] = []

@MainActor
open override class func discoverPreviews() -> [EMGDiscoveredPreview] {
previews = matchingPreviewTypes()
return previews.map { EMGDiscoveredPreview.from(previewType: $0) }
}

@MainActor
open override func test(_ preview: EMGPreview) {
let previewType = Self.previews.first { $0.typeName == preview.preview.typeName }
guard let preview = previewType?.previews[preview.index.intValue] else {
XCTFail("Preview not found")
return
}

var result: SnapshotResult? = nil
let expectation = XCTestExpectation()
renderingStrategy.render(preview: preview) { snapshotResult in
result = snapshotResult
expectation.fulfill()
}
wait(for: [expectation], timeout: 10)
guard let result else {
XCTFail("Did not render")
return
}
do {
let attachment = try XCTAttachment(image: result.image.get())
attachment.name = preview.displayName
attachment.lifetime = .keepAlways
add(attachment)
} catch {
XCTFail("Error \(error)")
}
}
}

0 comments on commit 17b2be4

Please sign in to comment.