Skip to content

Commit dd42de4

Browse files
authored
SwiftUI View Tests (pointfreeco#246)
* Snapshotting SwiftUI Views as Image * updated SwiftUITest * removed header comments * added .swiftpm to gitignore * updated tests for Xcode 11 * updates SwiftUI test * added comments * added tvOS test * fixed merge conflicts * updated macOS tests * updated tvOS tests * updated OS versions * added os check
1 parent 9d4919d commit dd42de4

17 files changed

+144
-16
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ playground.xcworkspace
3838
# Packages/
3939
# Package.pins
4040
.build/
41+
.swiftpm
4142
Package.resolved
4243

4344
# CocoaPods

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ test-ios:
1515
set -o pipefail && \
1616
xcodebuild test \
1717
-scheme SnapshotTesting_iOS \
18-
-destination platform="iOS Simulator,name=iPhone 11 Pro Max,OS=13.2.2" \
18+
-destination platform="iOS Simulator,name=iPhone 11 Pro Max,OS=13.3" \
1919

2020
test-swift:
2121
swift test
@@ -24,6 +24,6 @@ test-tvos:
2424
set -o pipefail && \
2525
xcodebuild test \
2626
-scheme SnapshotTesting_tvOS \
27-
-destination platform="tvOS Simulator,name=Apple TV 4K,OS=13.2" \
27+
-destination platform="tvOS Simulator,name=Apple TV 4K,OS=13.3" \
2828

2929
test-all: test-linux test-macos test-ios

SnapshotTesting.xcodeproj/project.pbxproj

+34-10
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@
5555
3FDA79CD7E2D26217A5132DA /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D57E3B929139A0C14AEBA7 /* NSView.swift */; };
5656
42B1EA1619E926A4391D176F /* Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76261A778A1B8C06EB7E1B4 /* Description.swift */; };
5757
42BCD8FC5433734A7BD80486 /* SnapshotTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CD2F74288CF52A13C9BB497 /* SnapshotTestCase.swift */; };
58+
431F526524633AAD00F256F4 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F526124633A9D00F256F4 /* SwiftUIView.swift */; };
59+
431F526724633AAE00F256F4 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F526124633A9D00F256F4 /* SwiftUIView.swift */; };
5860
436C10BC8313288D84733DC5 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D04C79E5D44F1874BBACB11 /* UIView.swift */; };
61+
436E95562463421500DE7A44 /* SnapshotTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA789F513A8B1E8EF5BF81E2 /* SnapshotTestingTests.swift */; };
62+
436E95572463437D00DE7A44 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431F526124633A9D00F256F4 /* SwiftUIView.swift */; };
5963
4521C5D2FE3CE1784C44A210 /* XCTAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8DE5E8A102E1012CD0C95 /* XCTAttachment.swift */; };
6064
45FAA85BB7198113155FD27F /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646ABA578B433C2F4DD4A086 /* Async.swift */; };
6165
468C9CBF306D8D8F87BC35AC /* SnapshotTesting.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3BBC3220F45BA5E71F819602 /* SnapshotTesting.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -95,7 +99,6 @@
9599
7B9F0DFC589C66887A37F0FB /* URLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7A398EA6B5594232933158 /* URLRequest.swift */; };
96100
7C48053A90BCB45B35C432F4 /* testUpdateSnapshot.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1FE81FC04984B93B0C27FA6 /* testUpdateSnapshot.1.swift */; };
97101
7EDE9C3850EC4AF1100E6AB7 /* WaitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86810F80142B1A3265C4F74 /* WaitTests.swift */; };
98-
8442C5AE49466F0F0CB6BD9C /* SnapshotTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA789F513A8B1E8EF5BF81E2 /* SnapshotTestingTests.swift */; };
99102
87D3745DBD172E3E6427190F /* Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F831EAAFB97204ECD0B879 /* Any.swift */; };
100103
8BDA8D067C1A9BDD7EFB1E78 /* String+SpecialCharacters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49092873A7A5D6DA623E352 /* String+SpecialCharacters.swift */; };
101104
8C428A751E6342E2CE46E4CB /* NSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB124D18325606D35B7027A6 /* NSViewController.swift */; };
@@ -265,6 +268,7 @@
265268
34D7CFCA42A4D2168436F6C1 /* Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Codable.swift; sourceTree = "<group>"; };
266269
3BBC3220F45BA5E71F819602 /* SnapshotTesting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnapshotTesting.framework; sourceTree = BUILT_PRODUCTS_DIR; };
267270
4055310797F1D4813ABD0359 /* SpriteKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpriteKit.swift; sourceTree = "<group>"; };
271+
431F526124633A9D00F256F4 /* SwiftUIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; };
268272
4796690CC049B52358D7173A /* testUpdateSnapshotWithShorterExtendedDelimiter2.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testUpdateSnapshotWithShorterExtendedDelimiter2.1.swift; sourceTree = "<group>"; };
269273
47F0BCE5A339D74CD4AE05C2 /* testCreateSnapshotSingleLine.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testCreateSnapshotSingleLine.1.swift; sourceTree = "<group>"; };
270274
48914A258708D7AC11DC12EF /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
@@ -506,6 +510,7 @@
506510
5580E7AC2CEBEA656780C7A9 /* SceneKit.swift */,
507511
4055310797F1D4813ABD0359 /* SpriteKit.swift */,
508512
04F429C62A87AC95E13E2D78 /* String.swift */,
513+
431F526124633A9D00F256F4 /* SwiftUIView.swift */,
509514
810407271A6CB90161DCE6A0 /* UIImage.swift */,
510515
0D04C79E5D44F1874BBACB11 /* UIView.swift */,
511516
5AAFA3BB389939B18F4D7187 /* UIViewController.swift */,
@@ -626,8 +631,6 @@
626631
isa = PBXProject;
627632
attributes = {
628633
LastUpgradeCheck = 1020;
629-
TargetAttributes = {
630-
};
631634
};
632635
buildConfigurationList = D9A4BF45876C849A308801A2 /* Build configuration list for PBXProject "SnapshotTesting" */;
633636
compatibilityVersion = "Xcode 10.0";
@@ -696,6 +699,7 @@
696699
4A95F6990FFA0B2F52CAC260 /* Internal.swift in Sources */,
697700
785ECE356ECA7C1564D99932 /* NSImage.swift in Sources */,
698701
3FDA79CD7E2D26217A5132DA /* NSView.swift in Sources */,
702+
436E95572463437D00DE7A44 /* SwiftUIView.swift in Sources */,
699703
08C8278A038756916D597D02 /* NSViewController.swift in Sources */,
700704
9399D33F028C4B110119B641 /* PlistEncoder.swift in Sources */,
701705
D5483007C407B1AC42A9E391 /* SceneKit.swift in Sources */,
@@ -732,6 +736,7 @@
732736
A9E8A45449313F3F4B1B4D3F /* Internal.swift in Sources */,
733737
9A1B06446DC5BE00E017958D /* NSImage.swift in Sources */,
734738
345FB3E29989E8B1DA53617A /* NSView.swift in Sources */,
739+
431F526524633AAD00F256F4 /* SwiftUIView.swift in Sources */,
735740
AEA906B0259465877DCED2AC /* NSViewController.swift in Sources */,
736741
EC4E922DF51C6B2C024A3737 /* PlistEncoder.swift in Sources */,
737742
06BA09CBE16A002564A3D098 /* SceneKit.swift in Sources */,
@@ -755,11 +760,11 @@
755760
buildActionMask = 2147483647;
756761
files = (
757762
76D4B4FEF767C6E546A261D1 /* InlineSnapshotTests.swift in Sources */,
758-
8442C5AE49466F0F0CB6BD9C /* SnapshotTestingTests.swift in Sources */,
759763
D5EED6FE1C8F85CE32392066 /* TestHelpers.swift in Sources */,
760764
4D36F6FD7EC4C9D5EFEB1B54 /* WaitTests.swift in Sources */,
761765
133A694B28958DA656061EC8 /* testCreateSnapshotEscapedNewlineLastLine.1.swift in Sources */,
762766
19CAE304A06A92323E7C225C /* testCreateSnapshotMultiLine.1.swift in Sources */,
767+
436E95562463421500DE7A44 /* SnapshotTestingTests.swift in Sources */,
763768
E7A986FC4CE72492A5A2AFD7 /* testCreateSnapshotSingleLine.1.swift in Sources */,
764769
F384274F4593F0BC4E8F94DC /* testCreateSnapshotWithExtendedDelimiter1.1.swift in Sources */,
765770
DE888645B9529A2CCDE24580 /* testCreateSnapshotWithExtendedDelimiter2.1.swift in Sources */,
@@ -840,6 +845,7 @@
840845
4E2EC852FBB3BF0C7E9D8AB0 /* Internal.swift in Sources */,
841846
CE53F597D12C921A3DA2E8B0 /* NSImage.swift in Sources */,
842847
FE3FC1518791D7B6DD23D42A /* NSView.swift in Sources */,
848+
431F526724633AAE00F256F4 /* SwiftUIView.swift in Sources */,
843849
8C428A751E6342E2CE46E4CB /* NSViewController.swift in Sources */,
844850
93F5DF248D854C8E353BCA25 /* PlistEncoder.swift in Sources */,
845851
9BADD5EA602342F6EC050CDE /* SceneKit.swift in Sources */,
@@ -980,7 +986,10 @@
980986
DYLIB_CURRENT_VERSION = 1;
981987
DYLIB_INSTALL_NAME_BASE = "@rpath";
982988
ENABLE_BITCODE = NO;
983-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
989+
FRAMEWORK_SEARCH_PATHS = (
990+
"$(inherited)",
991+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
992+
);
984993
INFOPLIST_FILE = Sources/Info.plist;
985994
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
986995
LD_RUNPATH_SEARCH_PATHS = (
@@ -1025,7 +1034,10 @@
10251034
DYLIB_CURRENT_VERSION = 1;
10261035
DYLIB_INSTALL_NAME_BASE = "@rpath";
10271036
ENABLE_BITCODE = NO;
1028-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1037+
FRAMEWORK_SEARCH_PATHS = (
1038+
"$(inherited)",
1039+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1040+
);
10291041
INFOPLIST_FILE = Sources/Info.plist;
10301042
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
10311043
LD_RUNPATH_SEARCH_PATHS = (
@@ -1104,7 +1116,10 @@
11041116
DYLIB_CURRENT_VERSION = 1;
11051117
DYLIB_INSTALL_NAME_BASE = "@rpath";
11061118
ENABLE_BITCODE = NO;
1107-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1119+
FRAMEWORK_SEARCH_PATHS = (
1120+
"$(inherited)",
1121+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1122+
);
11081123
INFOPLIST_FILE = Sources/Info.plist;
11091124
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
11101125
LD_RUNPATH_SEARCH_PATHS = (
@@ -1131,7 +1146,10 @@
11311146
DYLIB_CURRENT_VERSION = 1;
11321147
DYLIB_INSTALL_NAME_BASE = "@rpath";
11331148
ENABLE_BITCODE = NO;
1134-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1149+
FRAMEWORK_SEARCH_PATHS = (
1150+
"$(inherited)",
1151+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1152+
);
11351153
INFOPLIST_FILE = Sources/Info.plist;
11361154
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
11371155
LD_RUNPATH_SEARCH_PATHS = (
@@ -1158,7 +1176,10 @@
11581176
DYLIB_CURRENT_VERSION = 1;
11591177
DYLIB_INSTALL_NAME_BASE = "@rpath";
11601178
ENABLE_BITCODE = NO;
1161-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1179+
FRAMEWORK_SEARCH_PATHS = (
1180+
"$(inherited)",
1181+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1182+
);
11621183
INFOPLIST_FILE = Sources/Info.plist;
11631184
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
11641185
LD_RUNPATH_SEARCH_PATHS = (
@@ -1264,7 +1285,10 @@
12641285
DYLIB_CURRENT_VERSION = 1;
12651286
DYLIB_INSTALL_NAME_BASE = "@rpath";
12661287
ENABLE_BITCODE = NO;
1267-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1288+
FRAMEWORK_SEARCH_PATHS = (
1289+
"$(inherited)",
1290+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1291+
);
12681292
INFOPLIST_FILE = Sources/Info.plist;
12691293
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
12701294
LD_RUNPATH_SEARCH_PATHS = (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
import Foundation
3+
#if canImport(SwiftUI)
4+
import SwiftUI
5+
#endif
6+
7+
#if os(iOS) || os(tvOS)
8+
@available(iOS 13.0, tvOS 13.0, *)
9+
extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
10+
11+
/// A snapshot strategy for comparing SwiftUI Views based on pixel equality.
12+
public static var image: Snapshotting {
13+
return .image()
14+
}
15+
16+
/// A snapshot strategy for comparing SwiftUI Views based on pixel equality.
17+
///
18+
/// - Parameters:
19+
/// - config: A set of device configuration settings.
20+
/// - precision: The percentage of pixels that must match.
21+
/// - size: A view size override.
22+
/// - traits: A trait collection override.
23+
public static func image(
24+
on config: ViewImageConfig,
25+
precision: Float = 1,
26+
size: CGSize? = nil,
27+
traits: UITraitCollection = .init()
28+
)
29+
-> Snapshotting {
30+
return Snapshotting<UIViewController, UIImage>
31+
.image(on: config,
32+
precision: precision,
33+
size: size,
34+
traits: traits)
35+
.pullback(UIHostingController.init(rootView:))
36+
}
37+
38+
/// A snapshot strategy for comparing SwiftUI Views based on pixel equality.
39+
///
40+
/// - Parameters:
41+
/// - drawHierarchyInKeyWindow: Utilize the simulator's key window in order to render `UIAppearance` and `UIVisualEffect`s. This option requires a host application for your tests and will _not_ work for framework test targets.
42+
/// - precision: The percentage of pixels that must match.
43+
/// - size: A view size override.
44+
/// - traits: A trait collection override.
45+
public static func image(
46+
drawHierarchyInKeyWindow: Bool = false,
47+
precision: Float = 1,
48+
size: CGSize? = nil,
49+
traits: UITraitCollection = .init()
50+
)
51+
-> Snapshotting {
52+
return Snapshotting<UIViewController, UIImage>
53+
.image(drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
54+
precision: precision,
55+
size: size,
56+
traits: traits)
57+
.pullback(UIHostingController.init(rootView:))
58+
}
59+
}
60+
#endif
61+

Tests/SnapshotTestingTests/SnapshotTestingTests.swift

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SceneKit
77
#endif
88
#if canImport(SpriteKit)
99
import SpriteKit
10+
import SwiftUI
1011
#endif
1112
#if canImport(WebKit)
1213
import WebKit
@@ -836,6 +837,47 @@ final class SnapshotTestingTests: XCTestCase {
836837
}
837838
#endif
838839
}
840+
841+
@available(iOS 13.0, *)
842+
func testSwiftUIView_iOS() {
843+
#if os(iOS)
844+
struct MyView: SwiftUI.View {
845+
846+
var body: some SwiftUI.View {
847+
VStack {
848+
Text("What's the point?")
849+
List {
850+
Text("1")
851+
Text("2")
852+
Text("3")
853+
}
854+
}
855+
}
856+
}
857+
// record = true
858+
assertSnapshot(matching: MyView(), as: .image(on: .iPhoneSe))
859+
#endif
860+
}
861+
862+
@available(tvOS 13.0, *)
863+
func testSwiftUIView_tvOS() {
864+
#if os(tvOS)
865+
struct MyView: SwiftUI.View {
866+
867+
var body: some SwiftUI.View {
868+
VStack {
869+
Text("What's the point?")
870+
List {
871+
Text("1")
872+
Text("2")
873+
Text("3")
874+
}
875+
}
876+
}
877+
}
878+
assertSnapshot(matching: MyView(), as: .image(size: .init(width: 800, height: 600)))
879+
#endif
880+
}
839881
}
840882

841883
#if os(iOS)
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[ AF U ] h=--- v=--- NSButton "Push Me" f=(0,0,77,32) b=(-)
2-
[ A U ] h=--- v=--- NSButtonBezelView f=(0,0,77,32) b=(-)
3-
[ AF U ] h=--- v=--- NSButtonTextField "Push Me" f=(10,6.5,57,15) b=(-)
1+
[ AF ! U ] h=--- v=--- NSButton "Push Me" f=(0,0,77,32) b=(-)
2+
[ A ! U ] h=--- v=--- NSButtonBezelView f=(0,0,77,32) b=(-)
3+
[ AF ! U ] h=--- v=--- NSButtonTextField "Push Me" f=(10,6.5,57,15) b=(-)
44
A=autoresizesSubviews, C=canDrawConcurrently, D=needsDisplay, F=flipped, G=gstate, H=hidden (h=by ancestor), L=needsLayout (l=child needsLayout), U=needsUpdateConstraints (u=child needsUpdateConstraints), O=opaque, P=preservesContentDuringLiveResize, S=scaled/rotated, W=wantsLayer (w=ancestor wantsLayer), V=needsVibrancy (v=allowsVibrancy), #=has surface
Loading
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
[ A W U ] h=--- v=--- NSView f=(0,0,10,10) b=(-) => <_NSViewBackingLayer>
1+
[ A ! W U ] h=--- v=--- NSView f=(0,0,10,10) b=(-) => <_NSViewBackingLayer>
22
A=autoresizesSubviews, C=canDrawConcurrently, D=needsDisplay, F=flipped, G=gstate, H=hidden (h=by ancestor), L=needsLayout (l=child needsLayout), U=needsUpdateConstraints (u=child needsUpdateConstraints), O=opaque, P=preservesContentDuringLiveResize, S=scaled/rotated, W=wantsLayer (w=ancestor wantsLayer), V=needsVibrancy (v=allowsVibrancy), #=has surface
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)