From 411453dea6cb6b162622932c2477ea8621789a8e Mon Sep 17 00:00:00 2001 From: Rodrigo Fernandez Date: Tue, 10 Feb 2026 15:31:19 +0200 Subject: [PATCH] add snapshot testing --- .DS_Store | Bin 0 -> 8196 bytes .gitignore | 1 + .prefire.yml | 2 + .../xcschemes/xcschememanagement.plist | 14 ++ SampleWorkshopApp.xcodeproj/project.pbxproj | 167 ++++++-------- .../UserInterfaceState.xcuserstate | Bin 0 -> 36117 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 + .../xcschemes/xcschememanagement.plist | 14 ++ SampleWorkshopApp/AboutView.swift | 4 +- SampleWorkshopApp/SupportChatView.swift | 7 +- .../custom_template.stencil | 210 ++++++++++++++++++ 11 files changed, 317 insertions(+), 108 deletions(-) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 .prefire.yml create mode 100644 DesignSystem/.swiftpm/xcode/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 SampleWorkshopApp.xcodeproj/project.xcworkspace/xcuserdata/rodrigofernandez.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 SampleWorkshopApp.xcodeproj/xcuserdata/rodrigofernandez.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 SampleWorkshopApp.xcodeproj/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 SampleWorkshopAppTests/custom_template.stencil diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f89b663b7de8afcf3c20d193077241d32b8f3512 GIT binary patch literal 8196 zcmeHMO>7%Q6n-yBlU>4+Nt2e8Qf0x2-zeA(DWyf=IF3-8mR7YBCut$9z0+jLde`!- z?T9Fn5fvc80fFcZp~AHS6^R2v<>y4zs=xtNs*pHv;ee31aOcg=IwA^@D1VC79ckv9 z_vXE~^FFU<^lSjYOwCRK3;=*eT_WqI;ZK@emwiKPi!@z?Nd5pWa43KeRan|!9V=o6 zVg_OcVg_OcVg|MX19WH0rfqWXtJQdn8HgFUDH)LGgGpUtI>M&H@aUi+ECC2RNF6qs zQymccm=UHUY$^<0DW<9(FmlDH#6aav>!vg(Oh?#MsN4aSJ7Dx=j4BiipH6;eXbzZG z7>_XnF#{VJkhS}6$bkiAsI05s1uRk}Ux6{|6&QwPD3NSagqg59FkR_V5E!g=N_{tMc0s;`lhNwij zeNwErjtG*;YuMJ_v8}U9>(aWlXL5^vBNtdfO=PU?# zUE7g(_~428CAaMPugR?F%#6-fFqFJDzD~0nO4=3mDCxF~t)#ziC9U7meXDVsX&gE{ zdF1fX8K>lXIp+mF&w2#qeu%{5RbHo@3nkyJl|3=fdP|mDs<~Fc$6Qw$laAE{Kfy`o z)OnUxNYx`sRo``w`@#tv-4k{PWaVeJd8hQE$7$lw#x?}t4Mb-&8vbi zkxnWm^y@vWcY9y|j-9s;+;#6m$9d z?>=+bV5lkKfbqD&I&>zK_@pstFwm7SZcdm?)7T|V(-W=NB9VSjO%q=%G{ae#Qh#eb zLV2?f!u@anGB6DetiTyK3+LcGd+!7uO|B6eaQ4&WXf!cjbk6F7-EoWg0G z#W^e?$J4lo4puS18a8klU&NR248Dx7;2ZcRzJ+h&S$r4I;d}T#p2rXHL%e_=;iq^J zKf}-Q5?;nDcoo0KZ}3~Zjz8kh_^aAuTkFMtdi~x*{SCeN^<33+eQ$x{v|_Fg8A~w( zTZMskwUGf?|4(fG`~OziZoH3}ftZ2+fC22t=CT64DIsgN|({`VgO;`=}3Jsxgu27U)L&~LT? literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..197702b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/__Snapshots__/**/*.png \ No newline at end of file diff --git a/.prefire.yml b/.prefire.yml new file mode 100644 index 0000000..9608991 --- /dev/null +++ b/.prefire.yml @@ -0,0 +1,2 @@ +test_configuration: + template_file_path: custom_template.stencil \ No newline at end of file diff --git a/DesignSystem/.swiftpm/xcode/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist b/DesignSystem/.swiftpm/xcode/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..431e5ba --- /dev/null +++ b/DesignSystem/.swiftpm/xcode/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + DesignSystem.xcscheme_^#shared#^_ + + orderHint + 1 + + + + diff --git a/SampleWorkshopApp.xcodeproj/project.pbxproj b/SampleWorkshopApp.xcodeproj/project.pbxproj index 2bbd500..5723088 100644 --- a/SampleWorkshopApp.xcodeproj/project.pbxproj +++ b/SampleWorkshopApp.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 7ED0894B2F3B50BC0009DAB3 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 7ED0894A2F3B50BC0009DAB3 /* SnapshotTesting */; }; + 7ED089512F3B531E0009DAB3 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 7ED089502F3B531E0009DAB3 /* Prefire */; }; + 7ED089712F3B5AE00009DAB3 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 7ED089702F3B5AE00009DAB3 /* Prefire */; }; A74FDA202F3A2674000A32F5 /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = A74FDA212F3A2674000A32F5 /* DesignSystem */; }; /* End PBXBuildFile section */ @@ -18,19 +21,11 @@ remoteGlobalIDString = A74FD9E82F3A2672000A32F5; remoteInfo = SampleWorkshopApp; }; - A74FDA012F3A2673000A32F5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = A74FD9E12F3A2672000A32F5 /* Project object */; - proxyType = 1; - remoteGlobalIDString = A74FD9E82F3A2672000A32F5; - remoteInfo = SampleWorkshopApp; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ A74FD9E92F3A2672000A32F5 /* SampleWorkshopApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleWorkshopApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; A74FD9F62F3A2673000A32F5 /* SampleWorkshopAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleWorkshopAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A74FDA002F3A2673000A32F5 /* SampleWorkshopAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleWorkshopAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -56,6 +51,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7ED089712F3B5AE00009DAB3 /* Prefire in Frameworks */, A74FDA202F3A2674000A32F5 /* DesignSystem in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -64,25 +60,28 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A74FD9FD2F3A2673000A32F5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( + 7ED089512F3B531E0009DAB3 /* Prefire in Frameworks */, + 7ED0894B2F3B50BC0009DAB3 /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7ED0894F2F3B531E0009DAB3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; A74FD9E02F3A2672000A32F5 = { isa = PBXGroup; children = ( A74FD9EB2F3A2672000A32F5 /* SampleWorkshopApp */, A74FD9F92F3A2673000A32F5 /* SampleWorkshopAppTests */, A74FDA032F3A2673000A32F5 /* SampleWorkshopAppUITests */, + 7ED0894F2F3B531E0009DAB3 /* Frameworks */, A74FD9EA2F3A2672000A32F5 /* Products */, ); sourceTree = ""; @@ -92,7 +91,6 @@ children = ( A74FD9E92F3A2672000A32F5 /* SampleWorkshopApp.app */, A74FD9F62F3A2673000A32F5 /* SampleWorkshopAppTests.xctest */, - A74FDA002F3A2673000A32F5 /* SampleWorkshopAppUITests.xctest */, ); name = Products; sourceTree = ""; @@ -118,6 +116,7 @@ name = SampleWorkshopApp; packageProductDependencies = ( A74FDA212F3A2674000A32F5 /* DesignSystem */, + 7ED089702F3B5AE00009DAB3 /* Prefire */, ); productName = SampleWorkshopApp; productReference = A74FD9E92F3A2672000A32F5 /* SampleWorkshopApp.app */; @@ -134,6 +133,7 @@ buildRules = ( ); dependencies = ( + 7ED0894E2F3B52EE0009DAB3 /* PBXTargetDependency */, A74FD9F82F3A2673000A32F5 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( @@ -141,34 +141,13 @@ ); name = SampleWorkshopAppTests; packageProductDependencies = ( + 7ED0894A2F3B50BC0009DAB3 /* SnapshotTesting */, + 7ED089502F3B531E0009DAB3 /* Prefire */, ); productName = SampleWorkshopAppTests; productReference = A74FD9F62F3A2673000A32F5 /* SampleWorkshopAppTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - A74FD9FF2F3A2673000A32F5 /* SampleWorkshopAppUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = A74FDA102F3A2673000A32F5 /* Build configuration list for PBXNativeTarget "SampleWorkshopAppUITests" */; - buildPhases = ( - A74FD9FC2F3A2673000A32F5 /* Sources */, - A74FD9FD2F3A2673000A32F5 /* Frameworks */, - A74FD9FE2F3A2673000A32F5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - A74FDA022F3A2673000A32F5 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - A74FDA032F3A2673000A32F5 /* SampleWorkshopAppUITests */, - ); - name = SampleWorkshopAppUITests; - packageProductDependencies = ( - ); - productName = SampleWorkshopAppUITests; - productReference = A74FDA002F3A2673000A32F5 /* SampleWorkshopAppUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -186,10 +165,6 @@ CreatedOnToolsVersion = 16.4; TestTargetID = A74FD9E82F3A2672000A32F5; }; - A74FD9FF2F3A2673000A32F5 = { - CreatedOnToolsVersion = 16.4; - TestTargetID = A74FD9E82F3A2672000A32F5; - }; }; }; buildConfigurationList = A74FD9E42F3A2672000A32F5 /* Build configuration list for PBXProject "SampleWorkshopApp" */; @@ -203,6 +178,8 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( A74FDA222F3A2674000A32F5 /* XCLocalSwiftPackageReference "DesignSystem" */, + 7ED089492F3B50BC0009DAB3 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + 7ED0894C2F3B52DA0009DAB3 /* XCRemoteSwiftPackageReference "Prefire" */, ); preferredProjectObjectVersion = 77; productRefGroup = A74FD9EA2F3A2672000A32F5 /* Products */; @@ -211,7 +188,6 @@ targets = ( A74FD9E82F3A2672000A32F5 /* SampleWorkshopApp */, A74FD9F52F3A2673000A32F5 /* SampleWorkshopAppTests */, - A74FD9FF2F3A2673000A32F5 /* SampleWorkshopAppUITests */, ); }; /* End PBXProject section */ @@ -231,13 +207,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A74FD9FE2F3A2673000A32F5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -255,25 +224,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A74FD9FC2F3A2673000A32F5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - A74FD9F82F3A2673000A32F5 /* PBXTargetDependency */ = { + 7ED0894E2F3B52EE0009DAB3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = A74FD9E82F3A2672000A32F5 /* SampleWorkshopApp */; - targetProxy = A74FD9F72F3A2673000A32F5 /* PBXContainerItemProxy */; + productRef = 7ED0894D2F3B52EE0009DAB3 /* PrefireTestsPlugin */; }; - A74FDA022F3A2673000A32F5 /* PBXTargetDependency */ = { + A74FD9F82F3A2673000A32F5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = A74FD9E82F3A2672000A32F5 /* SampleWorkshopApp */; - targetProxy = A74FDA012F3A2673000A32F5 /* PBXContainerItemProxy */; + targetProxy = A74FD9F72F3A2673000A32F5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -487,38 +448,6 @@ }; name = Release; }; - A74FDA112F3A2673000A32F5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.SampleWorkshopAppUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = SampleWorkshopApp; - }; - name = Debug; - }; - A74FDA122F3A2673000A32F5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.SampleWorkshopAppUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = SampleWorkshopApp; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -549,15 +478,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A74FDA102F3A2673000A32F5 /* Build configuration list for PBXNativeTarget "SampleWorkshopAppUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A74FDA112F3A2673000A32F5 /* Debug */, - A74FDA122F3A2673000A32F5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ @@ -567,7 +487,46 @@ }; /* End XCLocalSwiftPackageReference section */ +/* Begin XCRemoteSwiftPackageReference section */ + 7ED089492F3B50BC0009DAB3 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tdrhq/swift-snapshot-testing"; + requirement = { + kind = exactVersion; + version = "1.18.6-sb"; + }; + }; + 7ED0894C2F3B52DA0009DAB3 /* XCRemoteSwiftPackageReference "Prefire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/BarredEwe/Prefire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.4.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ + 7ED0894A2F3B50BC0009DAB3 /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 7ED089492F3B50BC0009DAB3 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + 7ED0894D2F3B52EE0009DAB3 /* PrefireTestsPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 7ED0894C2F3B52DA0009DAB3 /* XCRemoteSwiftPackageReference "Prefire" */; + productName = "plugin:PrefireTestsPlugin"; + }; + 7ED089502F3B531E0009DAB3 /* Prefire */ = { + isa = XCSwiftPackageProductDependency; + package = 7ED0894C2F3B52DA0009DAB3 /* XCRemoteSwiftPackageReference "Prefire" */; + productName = Prefire; + }; + 7ED089702F3B5AE00009DAB3 /* Prefire */ = { + isa = XCSwiftPackageProductDependency; + package = 7ED0894C2F3B52DA0009DAB3 /* XCRemoteSwiftPackageReference "Prefire" */; + productName = Prefire; + }; A74FDA212F3A2674000A32F5 /* DesignSystem */ = { isa = XCSwiftPackageProductDependency; productName = DesignSystem; diff --git a/SampleWorkshopApp.xcodeproj/project.xcworkspace/xcuserdata/rodrigofernandez.xcuserdatad/UserInterfaceState.xcuserstate b/SampleWorkshopApp.xcodeproj/project.xcworkspace/xcuserdata/rodrigofernandez.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..557d6047fc86800b85c1f5d325831bc19364851d GIT binary patch literal 36117 zcmeIb2Vhgx_c;FUOJ3f4VWqTCN?SV8k&eu^Nn2LiB!%umX=%#{p-J0xByEzmfGF?D zkS&TL4urPsEui8+5yiohp*T@d9H279i6X!AGCC;ehyT~#*Z=SL`%<#rJNMpm?-}>p zbI(0*YD1mH;Z!Kz!4QUHJdBSKm;fU&VQECO+3v8|tYr~KdquScevON8+8RnDY*TB@ z6;4Ng49#0(5cSJ1$}vwjo6IECbVGI&2I!9xKHrV^gsTtO~2f>acpO5wl~>SPOO+HWOno7MqLB z!xmzTu%*}&*edKvY&Et9YsL0quV4qT*ReOS!`Kn*ICcVi7dwr8h<${8ihYLtiv5ON z!+yv9z^-F|VmAXOTBMlmgMxYF&MLMKM zc_<$hph8rHYEd1kM^>kpnqV6Pk_~#G(agAzFkUMk~-GXeD|Atw9^nCbSi8 zL)*~~^eozoj-z+bN9YT54t%|lEqIoi&oTuR>^HO-}ybPX}m&Y5!E9RB( z#`6rkDZFXCnY`J&IlQ^NCA_7)`+1M@R`FVSn|PafTX?&9dw559$9eDZ-sgS5JIni= z_XY2J-WA?Yyz9I_c{lhNpW@T}Fn%OIo}b81;;Z>1_#^pg{A_+ce=NV8KY{PyxA5=b z&*R_6pU+>$e}MlW{|WvY{#yQ4{x<$W{#*Rx{1f~S_#g5=;(y6M&;OqP6aQ!aFZ^Hm zzY&PQ35w`V^dSO>!9*YtLx8AZmE3NnEl zPL3c)l4)cnIf~37^T}~!FNQbP@z;96;6q%2r80Fpc1Jh zN=<2~WJ*WrsZ44VHJZw$@~9$e992q{QIn|}s)2G)PO6ETMKKgh-Am1;=1|M171X2D zW7OkRJGGWtM{T4wQLj)3s8^}isDsq&)Em^B)LYac>M->-b&7hAdY?K+eMx;!U8R1c zexiP(u2X-~IL)K^G)2>NAG$BypZ2Bw=|DP&4x=OK7&?VcrH9hP=;8DTdL*4jr_(uf zE}ci`(_?89T}wOY7J3>zlV<2S^n7|Ty@Xy$uctTA8|h8-W_k;~mEJ~gr+3gh={@ud z^sDr1^g;S<`Z#@pK1rXZKcqjWzo5URzoWmWFVdIjtMqU5HTs5-77B&EguR7*gnfmB zgaN|A!a!lPP$G;G#tP$v8ey_9MVKlaDl8Bd3dab?3X6o}gvG)V;dtRB;bdWjuwG~t z+Jw`EGlX{uS>e6Hg~CO}5!R-HymtS{CN6JfsC!14J-vRd%|Yw022EMV-;% zXv2K4{tV9W+Au$C0K;d|cTBNCz>kGI)t9Sz0`v(8r0RByIA4WbA|MzTVin2;h( zPsq?o6P56muFA}m>J&PiQkR~PlA%xcwmcYP4HY^5<#p1AdOp3|irNES!3QNEeu_UHH6T_nQ zOb(OFqD|b}W)^K@(TglP#-eYT3dUp*4H|2%0DSc(i_>QJVqZKq*I^J1)|o4fO?94o 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 + + diff --git a/SampleWorkshopApp.xcodeproj/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist b/SampleWorkshopApp.xcodeproj/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..5f612eb --- /dev/null +++ b/SampleWorkshopApp.xcodeproj/xcuserdata/rodrigofernandez.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + SampleWorkshopApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/SampleWorkshopApp/AboutView.swift b/SampleWorkshopApp/AboutView.swift index 787fd83..026a85f 100644 --- a/SampleWorkshopApp/AboutView.swift +++ b/SampleWorkshopApp/AboutView.swift @@ -78,14 +78,14 @@ struct AboutView: View { } } -#Preview("Light") { +#Preview("About_Light") { NavigationStack { AboutView() } .preferredColorScheme(.light) } -#Preview("Dark") { +#Preview("About_Dark") { NavigationStack { AboutView() } diff --git a/SampleWorkshopApp/SupportChatView.swift b/SampleWorkshopApp/SupportChatView.swift index cff5488..09d1b3f 100644 --- a/SampleWorkshopApp/SupportChatView.swift +++ b/SampleWorkshopApp/SupportChatView.swift @@ -7,6 +7,7 @@ import SwiftUI import DesignSystem +import Prefire struct ChatMessage: Identifiable { let id = UUID() @@ -101,12 +102,14 @@ extension SupportChatView { ] } -#Preview("Light") { +#Preview("SupportChatView_Light") { SupportChatView(messages: SupportChatView.sampleMessages) .preferredColorScheme(.light) + .prefireIgnored() } -#Preview("Dark") { +#Preview("SupportChatView_Dark") { SupportChatView(messages: SupportChatView.sampleMessages) .preferredColorScheme(.dark) + .prefireIgnored() } diff --git a/SampleWorkshopAppTests/custom_template.stencil b/SampleWorkshopAppTests/custom_template.stencil new file mode 100644 index 0000000..9207ccd --- /dev/null +++ b/SampleWorkshopAppTests/custom_template.stencil @@ -0,0 +1,210 @@ +// swiftlint:disable all +// swiftformat:disable all + +import XCTest +import SwiftUI +import Prefire +{% for import in argument.imports %} +import {{ import }} +{% endfor %} +{% if argument.mainTarget %} +@testable import {{ argument.mainTarget }} +{% endif %} +{% for import in argument.testableImports %} +@testable import {{ import }} +{% endfor %} +import SnapshotTesting +#if canImport(AccessibilitySnapshot) + import AccessibilitySnapshot +#endif + +@MainActor class {PREVIEW_FILE_NAME}Tests: XCTestCase, Sendable { + private var simulatorDevice: String?{% if argument.simulatorDevice %} = "{{ argument.simulatorDevice|default:nil }}"{% endif %} + private var requiredOSVersion: Int?{% if argument.simulatorOSVersion %} = {{ argument.simulatorOSVersion }}{% endif %} + private let snapshotDevices: [String]{% if argument.snapshotDevices %} = {{ argument.snapshotDevices|split:"|" }}{% else %} = []{% endif %} +#if os(iOS) + private let deviceConfig: DeviceConfig = ViewImageConfig.iPhoneX.deviceConfig +#elseif os(tvOS) + private let deviceConfig: DeviceConfig = ViewImageConfig.tv.deviceConfig +#endif + + + {% if argument.file %} + + private var file: StaticString { .init(stringLiteral: "{{ argument.file }}") } + {% endif %} + + @MainActor override func setUp() async throws { + try await super.setUp() + + checkEnvironments() + UIView.setAnimationsEnabled(false) + } + + // MARK: - PreviewProvider + + {% for type in types.types where type.implements.PrefireProvider or type.based.PrefireProvider or type|annotated:"PrefireProvider" %} + func test_{{ type.name|lowerFirstLetter|replace:"_Previews", "" }}() { + for preview in {{ type.name }}._allPreviews { + assertSnapshots(for: PrefireSnapshot(preview, device: preview.device?.snapshotDevice() ?? deviceConfig)) + } + } + {%- if not forloop.last %} + + {% endif %} + {% endfor %} + {% if argument.previewsMacrosDict %} + // MARK: - Macros + + {% for macroModel in argument.previewsMacrosDict %} + func test_{{ macroModel.componentTestName }}_Preview() { + {% if macroModel.properties %} + struct PreviewWrapper{{ macroModel.componentTestName }}: SwiftUI.View { + {{ macroModel.properties }} + var body: some View { + {{ macroModel.body|indent:12 }} + } + } + let preview = PreviewWrapper{{ macroModel.componentTestName }}.init + {% else %} + let preview = { + {{ macroModel.body|indent:8 }} + } + {% endif %} + {% if macroModel.isScreen == 1 %} + let isScreen = true + {% else %} + let isScreen = false + {% endif %} + assertSnapshots(for: PrefireSnapshot(preview, name: "{{ macroModel.displayName }}", isScreen: isScreen, device: deviceConfig)) + } + {%- if not forloop.last %} + + {% endif %} + {% endfor %} + {% endif %} + // MARK: Private + + private func assertSnapshots(for prefireSnapshot: PrefireSnapshot) -> String? { + guard !snapshotDevices.isEmpty else { + return assertSnapshot(for: prefireSnapshot) + } + + for deviceName in snapshotDevices { + var snapshot = prefireSnapshot + guard let device: DeviceConfig = PreviewDevice(rawValue: deviceName).snapshotDevice() else { + fatalError("Unknown device name from configuration file: \(deviceName)") + } + + snapshot.name = "\(prefireSnapshot.name)-\(deviceName)" + snapshot.device = device + + // Ignore specific device safe area + snapshot.device.safeArea = .zero + + // Ignore specific device display scale + snapshot.traits = UITraitCollection(displayScale: 2.0) + + assertSnapshot(for: snapshot) + } + + return nil + } + + private func assertSnapshot(for prefireSnapshot: PrefireSnapshot) -> String? { + let (previewView, preferences) = prefireSnapshot.loadViewWithPreferences() + + let failure = verifySnapshot( + of: previewView, + as: .wait( + for: preferences.delay, + on: .image( + precision: preferences.precision, + perceptualPrecision: preferences.perceptualPrecision, + layout: prefireSnapshot.isScreen ? .device(config: prefireSnapshot.device.imageConfig) : .sizeThatFits, + traits: prefireSnapshot.traits + ) + ), + record: preferences.record{% if argument.file %}, + file: file{% endif %}, + testName: prefireSnapshot.name + ) + + #if canImport(AccessibilitySnapshot) + let vc = UIHostingController(rootView: previewView) + vc.view.frame = UIScreen.main.bounds + + SnapshotTesting.assertSnapshot( + matching: vc, + as: .wait(for: preferences.delay, on: .accessibilityImage(showActivationPoints: .always)){% if argument.file %}, + record: preferences.record, + file: file{% endif %}, + testName: prefireSnapshot.name + ".accessibility" + ) + #endif + return failure + } + + /// Check environments to avoid problems with snapshots on different devices or OS. + private func checkEnvironments() { + if let simulatorDevice, let deviceModel = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { + guard deviceModel.contains(simulatorDevice) else { + fatalError("Switch to using \(simulatorDevice) for these tests. (You are using \(deviceModel))") + } + } + + if let requiredOSVersion { + let osVersion = ProcessInfo().operatingSystemVersion + guard osVersion.majorVersion == requiredOSVersion else { + fatalError("Switch to iOS \(requiredOSVersion) for these tests. (You are using \(osVersion))") + } + } + } +} + +// MARK: - SnapshotTesting + Extensions + +private extension DeviceConfig { + var imageConfig: ViewImageConfig { ViewImageConfig(safeArea: safeArea, size: size, traits: traits) } +} + +private extension ViewImageConfig { + var deviceConfig: DeviceConfig { DeviceConfig(safeArea: safeArea, size: size, traits: traits) } +} + +private extension PreviewDevice { + func snapshotDevice() -> ViewImageConfig? { + switch rawValue { + #if os(iOS) + case "iPhone 16 Pro Max", "iPhone 15 Pro Max", "iPhone 14 Pro Max", "iPhone 13 Pro Max", "iPhone 12 Pro Max": + return .iPhone13ProMax + case "iPhone 16 Pro", "iPhone 15 Pro", "iPhone 14 Pro", "iPhone 13 Pro", "iPhone 12 Pro": + return .iPhone13Pro + case "iPhone 16", "iPhone 15", "iPhone 14", "iPhone 13", "iPhone 12", "iPhone 11", "iPhone 10", "iPhone X": + return .iPhoneX + case "iPhone 6", "iPhone 6s", "iPhone 7", "iPhone 8", "iPhone SE (2nd generation)", "iPhone SE (3rd generation)": + return .iPhone8 + case "iPhone 6 Plus", "iPhone 6s Plus", "iPhone 8 Plus": + return .iPhone8Plus + case "iPhone SE (1st generation)": + return .iPhoneSe + case "iPad": + return .iPad10_2 + case "iPad Mini": + return .iPadMini + case "iPad Pro 11": + return .iPadPro11 + case "iPad Pro 12.9": + return .iPadPro12_9 + #elseif os(tvOS) + case "Apple TV": + return .tv + #endif + default: return nil + } + } + + func snapshotDevice() -> DeviceConfig? { + (self.snapshotDevice())?.deviceConfig + } +} \ No newline at end of file