Closed jflan-dd closed 11 months ago
@mackoj This should be a more flexible solution to #1
Thanks for making this.
This doesn't appear to work, this code:
func test_snapshots() {
MacOSContentView_Previews.snapshots.assertSnapshots(as: .image)
}
Results in compiler error:
Generic parameter 'Format' could not be inferred
Am I missing something?
@jflan-dd @mackoj
@andrewtheis The main swift-snapshot-testing
library doesn't ship with an NSImage
version of .image
out of the box. I defined one locally in tests within this project, but it felt weird to include it as part of the package since the this library is otherwise strategy agnostic.
You could try including this strategy in your project.
extension Snapshotting where Value: SwiftUI.View, Format == NSImage {
static var image: Self {
Snapshotting<NSView, NSImage>.image(size: .init(width: 400, height: 400)).pullback { view in
let view = NSHostingView(rootView: view)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
return view
}
}
}
@jflan-dd makes sense. This is code I used, which also correctly scales the image based on the backing scale factor - necessary if you're using Xcode Cloud, which runs at a 1px = 1pt resolution. Hope this helps anyone else that stumbles upon this!
#if os(macOS)
import AppKit
import Cocoa
extension Snapshotting where Value == NSView, Format == NSImage {
public static func scaleFactorAdjustedImage(
precision: Float = 1,
perceptualPrecision: Float = 1,
size: CGSize
) -> Snapshotting {
return SimplySnapshotting.image(
precision: precision, perceptualPrecision: perceptualPrecision
).asyncPullback { view in
Async { callback in
view.frame.size = size
guard view.frame.width > 0, view.frame.height > 0 else {
fatalError("View not renderable to image at size \(view.frame.size)")
}
let bitmapRep = view.bitmapImageRepForCachingDisplay(in: view.bounds)!
view.cacheDisplay(in: view.bounds, to: bitmapRep)
// Use the scaling function to get the scaled image
let image = adjustForBackingScaleFactor(bitmapRep, in: view)
callback(image)
}
}
}
// Function to scale the image based on the window's scale factor
static func adjustForBackingScaleFactor(_ bitmapRep: NSBitmapImageRep, in view: NSView) -> NSImage {
let scaleFactor = 1 / (NSScreen.main?.backingScaleFactor ?? view.window?.backingScaleFactor ?? 1)
let scaledSize = NSSize(width: bitmapRep.size.width * scaleFactor, height: bitmapRep.size.height * scaleFactor)
guard scaleFactor < 1, let scaledBitmapRep = bitmapRep.copy() as? NSBitmapImageRep else {
return NSImage(size: bitmapRep.size, flipped: false) { rect in
bitmapRep.draw(in: rect)
}
}
scaledBitmapRep.size = scaledSize
return NSImage(size: scaledSize, flipped: false) { rect in
scaledBitmapRep.draw(in: rect)
}
}
}
#endif
You'll need some additional code to wrap it in a NSHostingController and get the sizeThatFits
@andrewtheis Thanks for sharing this!
As a small nit-pick, is it possible to use plain .pullback
instead of .asyncPullback
since I don't see any asynchronous work happening via a callback?
Add macOS support to
Package.swift
and loosen hardUIImage
requirement to allow any snapshotting strategy that takesAnyView
/ModifiedView
as input.