@@ -9,21 +9,50 @@ extension Snapshotting where Value == NSView, Format == NSImage {
9
9
10
10
/// A snapshot strategy for comparing views based on pixel equality.
11
11
///
12
+ /// >This function calls to `NSView.cacheDisplay()` which has side-effects that cannot be undone. Under some circumstances
13
+ /// >subviews will be added (e.g. for `NSButton`-views) and `NSView.needsLayout` will be set to `false`. Keep that in mind
14
+ /// >when asserting with `.image` and `.recursiveDescription` within the same test.
15
+ ///
12
16
/// - Parameters:
13
17
/// - precision: The percentage of pixels that must match.
14
18
/// - size: A view size override.
19
+ /// - appearance: The appearance to use when drawing the view. Pass `nil` to use the view’s existing appearance.
20
+ /// - windowForDrawing: The choice of window to use when drawing the view. Pass `.nil` to ignore.
21
+ /// __Precondition__: If set to a none nil value, the view must not already
22
+ /// be attached to an existing window. (We wouldn’t be able to easily restore the view and all its
23
+ /// associated constraints to the original window after moving it to the new window.)
15
24
public static func image(
16
25
precision: Float = 1 ,
17
26
size: CGSize ? = nil ,
18
- appearance: NSAppearance ? = NSAppearance ( named: . aqua)
27
+ appearance: NSAppearance ? = NSAppearance ( named: . aqua) ,
28
+ windowForDrawing: GenericWindow ? = nil
19
29
) -> Snapshotting {
20
30
return SimplySnapshotting . image ( precision: precision) . asyncPullback { view in
21
- view . appearance = appearance ?? view . appearance
22
- let initialSize = view. frame. size
31
+
32
+ let initialFrame = view. frame
23
33
if let size = size { view. frame. size = size }
24
34
guard view. frame. width > 0 , view. frame. height > 0 else {
25
35
fatalError ( " View not renderable to image at size \( view. frame. size) " )
26
36
}
37
+
38
+ let initialAppearance = view. appearance
39
+ if let appearance = appearance {
40
+ view. appearance = appearance
41
+ }
42
+
43
+ if let windowForDrawing = windowForDrawing {
44
+ precondition (
45
+ view. window == nil ,
46
+ """
47
+ If choosing to draw the view using a new window, the view must not already be attached to an existing window. \
48
+ (We wouldn’t be able to easily restore the view and all its associated constraints to the original window \
49
+ after moving it to the new window.)
50
+ """
51
+ )
52
+ windowForDrawing. contentView = NSView ( )
53
+ windowForDrawing. contentView? . addSubview ( view)
54
+ }
55
+
27
56
return view. snapshot ?? Async { callback in
28
57
addImagesForRenderedViews ( view) . sequence ( ) . run { views in
29
58
let bitmapRep = view. bitmapImageRepForCachingDisplay ( in: view. bounds) !
@@ -32,7 +61,23 @@ extension Snapshotting where Value == NSView, Format == NSImage {
32
61
image. addRepresentation ( bitmapRep)
33
62
callback ( image)
34
63
views. forEach { $0. removeFromSuperview ( ) }
35
- view. frame. size = initialSize
64
+ if windowForDrawing != nil {
65
+ view. removeFromSuperview ( )
66
+ view. layer = nil
67
+ view. subviews. forEach { subview in
68
+ subview. layer = nil
69
+
70
+ }
71
+
72
+ // This is to maintain compatibility with `recursiveDescription` because the current
73
+ // test snapshots expect `.needsLayout = false` and for some apple magic reason
74
+ // `view.needsLayout = false` does not do anything, but this does.
75
+ let bitmapRep2 = view. bitmapImageRepForCachingDisplay ( in: view. bounds) !
76
+ view. cacheDisplay ( in: view. bounds, to: bitmapRep2)
77
+
78
+ }
79
+ view. appearance = initialAppearance
80
+ view. frame = initialFrame
36
81
}
37
82
}
38
83
}
@@ -50,4 +95,31 @@ extension Snapshotting where Value == NSView, Format == String {
50
95
}
51
96
}
52
97
}
98
+
99
+ /// A NSWindow which can be configured in a deterministic way.
100
+ public final class GenericWindow : NSWindow {
101
+ public init ( backingScaleFactor: CGFloat = 2.0 , colorSpace: NSColorSpace ? = nil ) {
102
+ self . _backingScaleFactor = backingScaleFactor
103
+ self . _explicitlySpecifiedColorSpace = colorSpace
104
+
105
+ super. init ( contentRect: NSRect . zero, styleMask: [ ] , backing: . buffered, defer: true )
106
+ }
107
+
108
+ private let _explicitlySpecifiedColorSpace : NSColorSpace ?
109
+ private var _systemSpecifiedColorspace : NSColorSpace ?
110
+
111
+ private let _backingScaleFactor : CGFloat
112
+ public override var backingScaleFactor : CGFloat {
113
+ return _backingScaleFactor
114
+ }
115
+
116
+ public override var colorSpace : NSColorSpace ? {
117
+ get {
118
+ _explicitlySpecifiedColorSpace ?? self . _systemSpecifiedColorspace
119
+ }
120
+ set {
121
+ self . _systemSpecifiedColorspace = newValue
122
+ }
123
+ }
124
+ }
53
125
#endif
0 commit comments