diff --git a/Sources/SnapshotPreferences/ExcludeTrait.swift b/Sources/SnapshotPreferences/ExcludeTrait.swift new file mode 100644 index 0000000..757d289 --- /dev/null +++ b/Sources/SnapshotPreferences/ExcludeTrait.swift @@ -0,0 +1,19 @@ +import SwiftUI +import SnapshotSharedModels + +@available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) +extension PreviewTrait where T == Preview.ViewTraits { + public static func snapshotExcluded(_ excluded: Bool) -> Self { + .modifier(excluded ? SnapshotInclusionMode.excluded : .automatic) + } + + public static var snapshotExcluded: Self { + .snapshotExcluded(true) + } +} + +extension SnapshotInclusionMode: @retroactive PreviewModifier { + public func body(content: Content, context: Void) -> some View { + content + } +} diff --git a/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift b/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift index dca6714..0a6aef2 100644 --- a/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift +++ b/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift @@ -9,6 +9,7 @@ public struct Preview: Identifiable { displayName = preview.displayName device = preview.device layout = preview.layout + _modifiers = [] _view = { ViewSelectorTree(SnapshotViewModel(index: preview.id)) { P.previews @@ -26,10 +27,14 @@ public struct Preview: Identifiable { let preview = Mirror(reflecting: preview) let traits = preview.descendant("traits")! as! [Any] var layout = PreviewLayout.device + var modifiers: [Any] = [] for t in traits { if let value = Mirror(reflecting: t).descendant("value") { if let value = value as? PreviewLayout { layout = value + } else if #available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *), + let traitModifiers = value as? [any PreviewModifier] { + modifiers += traitModifiers } else if String(describing: value).hasSuffix(".portraitUpsideDown") { orientation = .portraitUpsideDown } else if String(describing: value).hasSuffix(".landscapeLeft") { @@ -39,6 +44,7 @@ public struct Preview: Identifiable { } } } + self._modifiers = modifiers self.orientation = orientation self.layout = layout displayName = preview.descendant("displayName") as? String @@ -82,6 +88,14 @@ public struct Preview: Identifiable { public let index: Int public let device: PreviewDevice? public let layout: PreviewLayout + + // we can't store `[any PreviewModifier]` because it's unavailable before iOS 18 et al + private let _modifiers: [Any] + @available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) + public var modifiers: [any PreviewModifier] { + _modifiers as? [any PreviewModifier] ?? [] + } + private let _view: @MainActor () -> any View @MainActor public func view() -> any View { _view() diff --git a/Sources/SnapshotSharedModels/SnapshotInclusionMode.swift b/Sources/SnapshotSharedModels/SnapshotInclusionMode.swift new file mode 100644 index 0000000..3f83ce2 --- /dev/null +++ b/Sources/SnapshotSharedModels/SnapshotInclusionMode.swift @@ -0,0 +1,4 @@ +public enum SnapshotInclusionMode: Sendable { + case excluded + case automatic +} diff --git a/Sources/SnapshottingTests/PreviewLayoutTest.swift b/Sources/SnapshottingTests/PreviewLayoutTest.swift index 2435ffd..e2b33d3 100644 --- a/Sources/SnapshottingTests/PreviewLayoutTest.swift +++ b/Sources/SnapshottingTests/PreviewLayoutTest.swift @@ -60,6 +60,7 @@ open class PreviewLayoutTest: PreviewBaseTest, PreviewFilters { includedModules: Self.snapshotPreviewModules(), excludedModules: Self.excludedSnapshotPreviewModules() ) + .filter(SnapshotPreviewInclusionFilter.shouldInclude) return previews.map { DiscoveredPreview.from(previewType: $0) } } diff --git a/Sources/SnapshottingTests/SnapshotPreviewInclusionFilter.swift b/Sources/SnapshottingTests/SnapshotPreviewInclusionFilter.swift new file mode 100644 index 0000000..7550280 --- /dev/null +++ b/Sources/SnapshottingTests/SnapshotPreviewInclusionFilter.swift @@ -0,0 +1,10 @@ +@_implementationOnly import SnapshotPreviewsCore +import SnapshotSharedModels + +enum SnapshotPreviewInclusionFilter { + static func shouldInclude(preview: PreviewType) -> Bool { + guard #available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) else { return true } + guard preview.previews.count == 1 else { return true } + return !preview.previews[0].modifiers.contains { ($0 as? SnapshotInclusionMode) == .excluded } + } +} diff --git a/Sources/SnapshottingTests/SnapshotTest.swift b/Sources/SnapshottingTests/SnapshotTest.swift index df4e993..6e9d918 100644 --- a/Sources/SnapshottingTests/SnapshotTest.swift +++ b/Sources/SnapshottingTests/SnapshotTest.swift @@ -242,6 +242,8 @@ open class SnapshotTest: PreviewBaseTest, PreviewFilters { includedModules: Self.snapshotPreviewModules(), excludedModules: Self.excludedSnapshotPreviewModules() ) + .filter(SnapshotPreviewInclusionFilter.shouldInclude) + fileNameResolver = FileNameResolver(previews: previews) if let allSnapshotImageNamesWriter {