@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,109 +1,73 @@
1
- # Swift Testing Patterns Reference
1
+ # Swift Testing Patterns
2
2
 
3
- ## Contents
4
- - Basic Tests and Traits
5
- - Expectations and Requirements
6
- - Suite Organization
7
- - Parameterized Tests
8
- - Execution Model
9
- - Confirmation and Known Issues
10
- - Tags
11
- - TestScoping and Test Organization
12
- - XCTest Migration Patterns
13
- - Mocking and Test Doubles
14
- - Testable Architecture
15
- - Async and Concurrent Tests
16
- - XCTest UI Tests - Page Object Pattern
17
- - Performance Testing
18
- - Snapshot Testing
19
- - Test Attachments
20
- - Exit Testing
21
- - Test File Organization
22
- - What to Test
23
- - Common Mistakes and Review Checklist
24
-
25
- ## Basic Tests and Traits
26
-
27
- ```swift
28
- import Testing
29
-
30
- @Test("User can update their display name")
31
- func updateDisplayName() {
32
- var user = User(name: "Alice")
33
- user.name = "Bob"
34
- #expect(user.name == "Bob")
35
- }
3
+ Companion to the `swift-testing` skill.
36
4
 
37
- @Test(.tags(.validation, .email))
38
- func validatesEmailFormat() { /* ... */ }
39
- ```
5
+ ## Contents
40
6
 
41
- ## Expectations and Requirements
7
+ - [Suites](#suites)
8
+ - [Parameterized Tests](#parameterized-tests)
9
+ - [Execution Model](#execution-model)
10
+ - [Confirmations](#confirmations)
11
+ - [Known Issues Limited to Specific Failures](#known-issues-limited-to-specific-failures)
12
+ - [Tags](#tags)
13
+ - [Scoped Setup and Teardown with `TestScoping`](#scoped-setup-and-teardown-with-testscoping)
14
+ - [From XCTest, Step by Step](#from-xctest-step-by-step)
15
+ - [Test Doubles](#test-doubles)
16
+ - [Designing for Tests](#designing-for-tests)
17
+ - [Async and Time](#async-and-time)
18
+ - [UI Tests Stay on XCTest](#ui-tests-stay-on-xctest)
19
+ - [Performance Tests Stay on XCTest](#performance-tests-stay-on-xctest)
20
+ - [Snapshot Tests](#snapshot-tests)
21
+ - [Attachments from Files](#attachments-from-files)
22
+ - [Exit Tests](#exit-tests)
23
+ - [Organising Files](#organising-files)
24
+ - [Readable Arguments](#readable-arguments)
25
+ - [Tests That Need a Newer OS](#tests-that-need-a-newer-os)
26
+ - [More Mistakes to Avoid](#more-mistakes-to-avoid)
27
+
28
+ ## Suites
42
29
 
43
30
  ```swift
44
- #expect(result == 42)
45
- #expect(name.isEmpty == false)
46
- #expect(items.count > 0, "Items should not be empty")
47
-
48
- // Error type checking
49
- #expect(throws: ValidationError.self) {
50
- try validate(email: "not-an-email")
51
- }
52
-
53
- // Specific error matching
54
- #expect {
55
- try validate(email: "")
56
- } throws: { error in
57
- guard let err = error as? ValidationError else { return false }
58
- return err == .empty
59
- }
60
-
61
- // #require unwraps or fails the test
62
- let user = try #require(await fetchUser(id: 1))
63
- let first = try #require(items.first)
64
- ```
65
-
66
- **Rule: Use `#require` when subsequent assertions depend on the value. Use `#expect` for independent checks.**
67
-
68
- ## Suite Organization
31
+ import Testing
32
+ @testable import Ledger
69
33
 
70
- ```swift
71
- @Suite("User Authentication")
72
- struct AuthTests {
73
- let service: AuthService
74
- let mockRepo: MockUserRepository
34
+ @Suite("Sign-in")
35
+ struct SignInTests {
36
+ let gateway: FakeAuthGateway
37
+ let session: SessionController
75
38
 
76
- // init() replaces setUp() -- runs before each test
77
39
  init() {
78
- mockRepo = MockUserRepository()
79
- service = AuthService(repository: mockRepo)
40
+ gateway = FakeAuthGateway()
41
+ session = SessionController(gateway: gateway)
80
42
  }
81
43
 
82
- @Test func loginSucceeds() async throws {
83
- let user = try await service.login(email: "test@test.com", password: "pass")
84
- #expect(user.email == "test@test.com")
44
+ @Test func acceptsKnownUser() async throws {
45
+ let user = try await session.signIn(email: "ada@example.com", password: "pw")
46
+ #expect(user.email == "ada@example.com")
85
47
  }
86
48
 
87
- @Test func loginFailsWithBadPassword() async {
88
- #expect(throws: AuthError.invalidCredentials) {
89
- try await service.login(email: "test@test.com", password: "wrong")
49
+ @Test func rejectsWrongPassword() async {
50
+ await #expect(throws: AuthError.wrongPassword) {
51
+ try await session.signIn(email: "ada@example.com", password: "nope")
90
52
  }
91
53
  }
92
54
  }
93
55
  ```
94
56
 
95
- Suites can nest for logical grouping:
57
+ `init()` takes the place of `setUp()` and runs before every test, because each
58
+ test gets a new instance of the suite. Passing an error value, as in
59
+ `#expect(throws: AuthError.wrongPassword)`, checks for that exact error.
60
+
61
+ Suites nest for grouping:
96
62
 
97
63
  ```swift
98
- @Suite("Payments")
99
- struct PaymentTests {
100
- @Suite("Subscriptions")
101
- struct SubscriptionTests {
102
- @Test func renewsAutomatically() { /* ... */ }
64
+ @Suite("Currency")
65
+ struct CurrencyTests {
66
+ @Suite("Parsing") struct Parsing {
67
+ @Test func parsesEuro() { #expect(Money(parsing: "EUR 5")?.amount == 5) }
103
68
  }
104
- @Suite("One-Time")
105
- struct OneTimeTests {
106
- @Test func chargesCorrectAmount() { /* ... */ }
69
+ @Suite("Rounding") struct Rounding {
70
+ @Test func roundsHalfEven() { #expect(Money(2.345, "EUR").rounded.amount == 2.34) }
107
71
  }
108
72
  }
109
73
  ```
@@ -111,522 +75,442 @@ struct PaymentTests {
111
75
  ## Parameterized Tests
112
76
 
113
77
  ```swift
114
- @Test("Email validation", arguments: [
115
- ("user@example.com", true),
116
- ("user@", false),
117
- ("@example.com", false),
78
+ @Test("Postcode validation", arguments: [
79
+ ("10115", true),
80
+ ("ABCDE", false),
118
81
  ("", false),
119
82
  ])
120
- func validateEmail(email: String, isValid: Bool) {
121
- #expect(EmailValidator.isValid(email) == isValid)
83
+ func validatesPostcode(_ input: String, _ isValid: Bool) {
84
+ #expect(Postcode.isValid(input) == isValid)
122
85
  }
123
86
 
124
- // From CaseIterable
125
- @Test(arguments: Currency.allCases)
126
- func currencyHasSymbol(currency: Currency) {
127
- #expect(currency.symbol.isEmpty == false)
87
+ @Test(arguments: Weekday.allCases)
88
+ func everyWeekdayHasAShortName(_ day: Weekday) {
89
+ #expect(!day.shortName.isEmpty)
128
90
  }
129
91
 
130
- // Two collections: cartesian product
131
- @Test(arguments: [1, 2, 3], ["a", "b"])
132
- func combinations(number: Int, letter: String) {
133
- #expect(number > 0)
92
+ @Test(arguments: [Plan.basic, .pro], [Region.eu, .us])
93
+ func priceExistsForEveryPlanAndRegion(_ plan: Plan, _ region: Region) {
94
+ #expect(PriceTable.price(for: plan, in: region) != nil)
134
95
  }
135
96
 
136
- // Use zip for 1:1 pairing
137
- @Test(arguments: zip(["USD", "EUR"], ["$", "€"]))
138
- func currencySymbols(code: String, symbol: String) {
139
- #expect(Currency(code: code).symbol == symbol)
97
+ @Test(arguments: zip(["1", "22", "333"], [1, 2, 3]))
98
+ func digitCount(_ text: String, _ count: Int) {
99
+ #expect(text.count == count)
140
100
  }
141
101
  ```
142
102
 
143
- Each argument combination runs as an independent test case reported separately.
103
+ - Two argument collections test every combination (a cartesian product).
104
+ - `zip` pairs them one to one instead.
105
+ - Every combination is its own test case with its own result.
144
106
 
145
107
  ## Execution Model
146
108
 
147
- Swift Testing uses Swift Concurrency and runs tests in parallel by default. Treat every test as isolated work unless you explicitly serialize a scope.
109
+ Swift Testing schedules tests as concurrent tasks, so many of them execute at
110
+ once.
148
111
 
149
112
  ```swift
150
113
  @Suite(.serialized, .tags(.database))
151
- struct DatabaseTests {
152
- @Test func insertsRecord() async throws { /* ... */ }
153
- @Test func removesRecord() async throws { /* ... */ }
114
+ struct MigrationTests {
115
+ @Test func upgradesV1() throws { }
116
+ @Test func upgradesV2() throws { }
154
117
  }
155
118
  ```
156
119
 
157
- Use `.serialized` when tests must not overlap because they touch shared external state like a keychain, database, singleton service, or filesystem location. It does not make unrelated tests outside the serialized scope run one-at-a-time.
158
-
159
- Important implications:
160
- - Each test gets its own suite instance.
161
- - Declaration order is not a contract.
162
- - If one logical workflow depends on previous state, keep that workflow inside one test.
163
- - Prefer isolated fixtures over shared mutable globals.
164
-
165
- ## Confirmation and Known Issues
120
+ Reach for `.serialized` when tests share something outside the process that
121
+ cannot be isolated: the keychain, a database, a singleton service, a fixed
122
+ location on disk. Even then every test receives a new suite value, the order
123
+ of declarations promises nothing, and fixtures that isolate state remain the
124
+ better default.
166
125
 
167
- ### Confirmation (Async Event Testing)
126
+ ## Confirmations
168
127
 
169
128
  ```swift
170
- // Basic confirmation -- event must fire exactly once
171
- await confirmation("Received notification") { confirm in
172
- let observer = NotificationCenter.default.addObserver(
173
- forName: .userLoggedIn, object: nil, queue: .main
174
- ) { _ in confirm() }
175
- await authService.login()
176
- NotificationCenter.default.removeObserver(observer)
177
- }
129
+ @Test func postsSignedOutNotification() async {
130
+ let center = NotificationCenter()
131
+ let session = SessionController(notificationCenter: center)
178
132
 
179
- // Expected count -- event must fire exactly N times
180
- await confirmation("Received 3 items", expectedCount: 3) { confirm in
181
- processor.onItem = { _ in confirm() }
182
- await processor.process(items)
133
+ await confirmation("signed-out posted") { signedOut in
134
+ let token = center.addObserver(forName: .sessionEnded, object: nil, queue: nil) { _ in
135
+ signedOut()
136
+ }
137
+ await session.signOut()
138
+ center.removeObserver(token)
139
+ }
183
140
  }
184
- ```
185
-
186
- ### Known Issues
187
141
 
188
- ```swift
189
- // Known failing test -- does not count as failure
190
- withKnownIssue("Propane tank is empty") {
191
- #expect(truck.grill.isHeating)
142
+ @Test func reportsEveryChunk() async {
143
+ let uploader = ChunkUploader(chunks: 3)
144
+ await confirmation("chunk sent", expectedCount: 3) { chunkSent in
145
+ uploader.onChunk = { _ in chunkSent() }
146
+ await uploader.run()
147
+ }
192
148
  }
149
+ ```
193
150
 
194
- // Intermittent / flaky
195
- withKnownIssue(isIntermittent: true) {
196
- #expect(service.isReachable)
197
- }
151
+ Without `expectedCount`, the confirmation must fire exactly once. With it, it
152
+ must fire exactly that many times.
198
153
 
199
- // Conditional
200
- withKnownIssue {
201
- #expect(foodTruck.grill.isHeating)
202
- } when: {
203
- !hasPropane
204
- }
154
+ ## Known Issues Limited to Specific Failures
205
155
 
206
- // Match specific issues only
207
- try withKnownIssue {
208
- let level = try #require(foodTruck.batteryLevel)
209
- #expect(level >= 0.8)
210
- } matching: { issue in
211
- guard case .expectationFailed(let expectation) = issue.kind else { return false }
212
- return expectation.isRequired
156
+ ```swift
157
+ @Test func vendorFeedParses() throws {
158
+ try withKnownIssue {
159
+ let feed = try #require(VendorFeed(data: Fixtures.brokenFeed))
160
+ #expect(feed.items.count == 12)
161
+ } matching: { issue in
162
+ if case .expectationFailed(let expectation) = issue.kind {
163
+ return expectation.isRequired
164
+ }
165
+ return false
166
+ }
213
167
  }
214
168
  ```
215
169
 
216
- If no known issues are recorded, Swift Testing records a distinct issue notifying you the problem may be resolved.
170
+ The `matching:` closure decides which issues count as known. Here only a
171
+ failed `#require` is excused; any other failure still fails the test. The call
172
+ needs `try` because the body uses `#require`.
217
173
 
218
174
  ## Tags
219
175
 
220
- Tags must be declared as static members in an extension on `Tag`:
221
-
222
176
  ```swift
223
177
  extension Tag {
224
- @Tag static var critical: Self
225
- @Tag static var slow: Self
226
- @Tag static var networking: Self
227
178
  @Tag static var validation: Self
179
+ @Tag static var banking: Self
180
+ @Tag static var slow: Self
228
181
  }
229
-
230
- @Test(.tags(.critical, .networking))
231
- func apiCallReturnsData() async throws { /* ... */ }
232
182
  ```
233
183
 
234
- Filter tests by tag in Xcode test plans or CLI (tag-based filtering syntax varies by toolchain - verify for your Swift version).
184
+ Filter by tag in an Xcode test plan or from the command line. The command-line
185
+ syntax has changed between toolchains, so check it for the Swift version in
186
+ use.
235
187
 
236
- ## TestScoping and Test Organization
188
+ ## Scoped Setup and Teardown with `TestScoping`
237
189
 
238
- `TestScoping` consolidates per-test setup/teardown into reusable fixtures when attached through a custom trait:
190
+ `TestScoping` (Swift 6.1) wraps a test or suite in custom setup and teardown.
239
191
 
240
192
  ```swift
241
- struct DatabaseScope: TestTrait, SuiteTrait, TestScoping {
193
+ struct TemporaryDatabaseTrait: TestTrait, SuiteTrait, TestScoping {
242
194
  func provideScope(
243
195
  for test: Test,
244
196
  testCase: Test.Case?,
245
- performing body: @Sendable () async throws -> Void
197
+ performing function: @Sendable () async throws -> Void
246
198
  ) async throws {
247
- let db = try await TestDatabase.create()
199
+ let database = try await TestDatabase.create()
248
200
  do {
249
- try await body()
250
- try await db.destroy()
201
+ try await function()
251
202
  } catch {
252
- try? await db.destroy()
203
+ try? await database.destroy()
253
204
  throw error
254
205
  }
206
+ try await database.destroy()
255
207
  }
256
208
  }
257
209
 
258
- extension Trait where Self == DatabaseScope {
259
- static var databaseScope: Self { .init() }
210
+ extension Trait where Self == TemporaryDatabaseTrait {
211
+ static var temporaryDatabase: Self { Self() }
260
212
  }
261
213
 
262
- @Test(.databaseScope, .tags(.database))
263
- func insertsRecord() async throws {
264
- // Test runs inside DatabaseScope.provideScope
265
- }
214
+ @Test(.temporaryDatabase, .tags(.database))
215
+ func insertsRows() async throws { }
266
216
  ```
267
217
 
268
- ## XCTest Migration Patterns
218
+ Create the resource, run the test body, then clean up. If the body throws,
219
+ clean up with `try?` and rethrow the original error.
220
+
221
+ ## From XCTest, Step by Step
269
222
 
270
- Swift Testing tests are functions annotated with `@Test`; they do not need `XCTestCase`. Use the smallest shape that needs the fixture:
223
+ Possible shapes after migration:
271
224
 
272
225
  ```swift
273
- @Test func validatesTotal() {
274
- #expect(Cart(items: [.sample]).total == 9.99)
226
+ @Test func trimsWhitespace() {
227
+ #expect(" a ".trimmed == "a")
275
228
  }
276
229
 
277
- @Suite("Checkout")
278
- struct CheckoutTests {
279
- let calculator = PriceCalculator()
280
-
281
- @Test func appliesDiscount() {
282
- #expect(calculator.total(discount: .percent(10)) == 8.99)
283
- }
230
+ @Suite struct TaxTests {
231
+ let calculator = TaxCalculator(rate: 0.2)
232
+ @Test func addsTax() { #expect(calculator.gross(100) == 120) }
284
233
  }
285
234
 
286
- @Suite("Shared Cache")
287
- actor CacheTests {
288
- var cache = TestCache()
289
-
290
- @Test func storesValue() async {
291
- await cache.store("value", forKey: "key")
292
- #expect(await cache.value(forKey: "key") == "value")
293
- }
235
+ @Suite actor ThumbnailCacheTests {
236
+ var cache = ThumbnailCache()
237
+ @Test func storesImage() async { await cache.store(Data(), for: "a") }
294
238
  }
295
239
 
296
- struct PureHelperTests {
297
- @Test static func normalizesInput() {
298
- #expect(normalize(" email@example.com ") == "email@example.com")
299
- }
240
+ struct SlugTests {
241
+ @Test static func lowercases() { #expect(Slug("Hello").value == "hello") }
300
242
  }
301
243
  ```
302
244
 
303
- Common XCTest mappings:
304
-
305
245
  | XCTest | Swift Testing |
306
- |---|---|
307
- | `XCTAssertTrue(x)` / `XCTAssert(x)` | `#expect(x)` |
308
- | `XCTAssertFalse(x)` | `#expect(!x)` |
309
- | `XCTAssertEqual(a, b)` | `#expect(a == b)` |
310
- | `XCTAssertThrowsError(try f())` | `#expect(throws: (any Error).self) { try f() }` |
311
- | `XCTAssertNoThrow(try f())` | `#expect(throws: Never.self) { try f() }` |
312
- | `try XCTUnwrap(value)` | `try #require(value)` |
313
- | `XCTFail("message")` | `Issue.record("message")` |
314
-
315
- Convert `setUp` into isolated suite `init()` state. Avoid moving fixtures into singletons or shared globals; Swift Testing runs tests in parallel by default. Use actors or per-test fixtures for mutable test doubles, and use `.serialized` only when an external shared resource cannot be isolated.
316
-
317
- ## Mocking and Test Doubles
318
-
319
- Define testable boundaries with protocols:
246
+ |--------|---------------|
247
+ | `XCTAssert(isOpen)` or `XCTAssertTrue(isOpen)` | `#expect(isOpen)` |
248
+ | `XCTAssertFalse(isOpen)` | `#expect(!isOpen)` |
249
+ | `XCTAssertEqual(total, 120)` | `#expect(total == 120)` |
250
+ | `XCTAssertThrowsError(try parse(raw))` | `#expect(throws: (any Error).self) { try parse(raw) }` |
251
+ | `XCTAssertNoThrow(try parse(raw))` | `#expect(throws: Never.self) { try parse(raw) }` |
252
+ | `let user = try XCTUnwrap(maybeUser)` | `let user = try #require(maybeUser)` |
253
+ | `XCTFail("unreachable")` | `Issue.record("unreachable")` |
254
+
255
+ - Turn `setUp` into the suite's `init()`, one fresh fixture per test.
256
+ - Never move fixtures into singletons or globals to share them.
257
+ - Mutable test doubles belong in an actor or in a fixture created per test.
258
+ - Keep `.serialized` for the case where an outside resource truly cannot be
259
+ given to each test separately.
260
+
261
+ ## Test Doubles
320
262
 
321
263
  ```swift
322
- protocol UserRepository: Sendable {
323
- func fetch(id: String) async throws -> User
324
- func save(_ user: User) async throws
264
+ protocol ProfileStore: Sendable {
265
+ func profile(id: UUID) async throws -> Profile
266
+ func save(_ profile: Profile) async throws
325
267
  }
326
268
 
327
- actor MockUserRepository: UserRepository {
328
- var users: [String: User] = [:]
329
- var fetchError: (any Error)?
330
- private(set) var savedUsers: [User] = []
269
+ actor FakeProfileStore: ProfileStore {
270
+ var stored: Profile?
271
+ var failure: Error?
272
+ private(set) var saved: [Profile] = []
331
273
 
332
- init(users: [String: User] = [:], fetchError: (any Error)? = nil) {
333
- self.users = users
334
- self.fetchError = fetchError
274
+ init(stored: Profile? = nil, failure: Error? = nil) {
275
+ self.stored = stored
276
+ self.failure = failure
335
277
  }
336
278
 
337
- func fetch(id: String) async throws -> User {
338
- if let error = fetchError { throw error }
339
- guard let user = users[id] else { throw NotFoundError() }
340
- return user
279
+ func profile(id: UUID) async throws -> Profile {
280
+ if let failure { throw failure }
281
+ guard let stored else { throw StoreError.notFound }
282
+ return stored
341
283
  }
342
284
 
343
- func save(_ user: User) async throws {
344
- savedUsers.append(user)
345
- users[user.id] = user
285
+ func save(_ profile: Profile) async throws {
286
+ saved.append(profile)
346
287
  }
347
288
  }
348
289
  ```
349
290
 
350
- **Pattern:** Mocks conform to protocols, never subclass concrete types. For parallel Swift Testing runs, keep mutable mock state isolated in an actor or another Sendable-safe fixture. Store call counts and arguments for verification behind that isolation boundary.
291
+ Doubles conform to a protocol; they never subclass a concrete type. Keep their
292
+ mutable state, call counts and captured arguments behind an actor or other
293
+ `Sendable` isolation.
351
294
 
352
- ## Testable Architecture
353
-
354
- Inject dependencies through initializers for testability:
295
+ ## Designing for Tests
355
296
 
356
297
  ```swift
298
+ @MainActor
357
299
  @Observable
358
- class ProfileViewModel {
359
- var user: User?
360
- var error: Error?
361
- private let repository: any UserRepository
300
+ final class ProfileScreenModel {
301
+ private let store: any ProfileStore
302
+ private(set) var profile: Profile?
303
+ private(set) var failure: Error?
362
304
 
363
- init(repository: any UserRepository) {
364
- self.repository = repository
365
- }
305
+ init(store: any ProfileStore) { self.store = store }
366
306
 
367
- func load() async {
368
- do {
369
- user = try await repository.fetch(id: "current")
370
- } catch {
371
- self.error = error
372
- }
307
+ func load(id: UUID) async {
308
+ do { profile = try await store.profile(id: id) }
309
+ catch { failure = error }
373
310
  }
374
311
  }
375
312
 
376
- // Test with mock
377
- @Test @MainActor func viewModelLoadsUser() async {
378
- let mock = MockUserRepository(users: ["current": .preview])
379
- let vm = ProfileViewModel(repository: mock)
380
- await vm.load()
381
- #expect(vm.user?.name == "Alice")
313
+ @Test @MainActor func loadShowsProfile() async {
314
+ let expected = Profile(id: UUID(), name: "Ada")
315
+ let model = ProfileScreenModel(store: FakeProfileStore(stored: expected))
316
+ await model.load(id: expected.id)
317
+ #expect(model.profile == expected)
382
318
  }
383
319
 
384
- @Test @MainActor func viewModelHandlesError() async {
385
- let mock = MockUserRepository(fetchError: URLError(.notConnectedToInternet))
386
- let vm = ProfileViewModel(repository: mock)
387
- await vm.load()
388
- #expect(vm.user == nil)
389
- #expect(vm.error != nil)
320
+ @Test @MainActor func loadFailureLeavesNoProfile() async {
321
+ let model = ProfileScreenModel(store: FakeProfileStore(failure: StoreError.offline))
322
+ await model.load(id: UUID())
323
+ #expect(model.profile == nil)
324
+ #expect(model.failure != nil)
390
325
  }
391
326
  ```
392
327
 
393
- ## Async and Concurrent Tests
328
+ Dependencies come in through the initialiser, so tests pass a double.
329
+
330
+ ## Async and Time
331
+
332
+ Mark a test `@MainActor` when it drives a main-actor model, as above.
333
+
334
+ For time-based behaviour, inject a `Clock` and drive it by hand in the test.
335
+ The standard library has no manual test clock, so write a small one or use a
336
+ clock library.
394
337
 
395
338
  ```swift
396
- @Test @MainActor func viewModelUpdatesOnMainActor() async {
397
- let vm = ProfileViewModel(repository: MockUserRepository())
398
- await vm.load()
399
- #expect(vm.user != nil)
400
- }
339
+ @Test func debouncerWaitsForQuietPeriod() async {
340
+ let clock = ManualClock()
341
+ let search = DebouncedSearch(delay: .seconds(1), clock: clock)
342
+ search.queryChanged("sw")
401
343
 
402
- // Clock injection for time-dependent logic
403
- @Test func debounceUsesCorrectDelay() async throws {
404
- let clock = TestClock()
405
- let debouncer = Debouncer(delay: .seconds(1), clock: clock)
406
- debouncer.submit { /* action */ }
407
- await clock.advance(by: .milliseconds(500))
408
- #expect(!debouncer.hasExecuted)
409
- await clock.advance(by: .milliseconds(500))
410
- #expect(debouncer.hasExecuted)
344
+ await clock.advance(by: .milliseconds(600))
345
+ #expect(await search.requestCount == 0)
346
+
347
+ await clock.advance(by: .milliseconds(400))
348
+ #expect(await search.requestCount == 1)
411
349
  }
412
350
 
413
- // Error path testing
414
- @Test func fetchThrowsOnNetworkError() async {
415
- let mock = MockUserRepository(fetchError: URLError(.notConnectedToInternet))
416
- #expect(throws: URLError.self) {
417
- try await mock.fetch(id: "1")
351
+ @Test func offlineFetchThrows() async {
352
+ await #expect(throws: URLError.self) {
353
+ try await FeedClient(session: .offline).latest()
418
354
  }
419
355
  }
420
356
  ```
421
357
 
422
- ## XCTest UI Tests - Page Object Pattern
358
+ ## UI Tests Stay on XCTest
423
359
 
424
- Swift Testing does not support UI testing. Use XCTest with XCUITest for all UI tests.
360
+ Swift Testing has no UI testing support; use XCTest with XCUITest.
425
361
 
426
362
  ```swift
427
- class LoginUITests: XCTestCase {
428
- let app = XCUIApplication()
363
+ import XCTest
364
+
365
+ @MainActor
366
+ final class CheckoutUITests: XCTestCase {
367
+ var app: XCUIApplication!
429
368
 
430
- override func setUpWithError() throws {
369
+ override func setUp() async throws {
431
370
  continueAfterFailure = false
371
+ app = XCUIApplication()
432
372
  app.launch()
433
373
  }
434
374
 
435
- func testLoginFlow() throws {
436
- let loginPage = LoginPage(app: app)
437
- let homePage = loginPage.login(email: "test@test.com", password: "password")
438
- XCTAssertTrue(homePage.welcomeLabel.exists)
375
+ func testGuestCanReachPayment() {
376
+ let payment = SignInPage(app: app).continueAsGuest().addFirstItem().checkout()
377
+ XCTAssertTrue(payment.payButton.waitForExistence(timeout: 5))
439
378
  }
440
379
  }
441
- ```
442
-
443
- ### Page Object Pattern
444
-
445
- Encapsulate UI element queries in page objects for reusable, readable UI tests:
446
380
 
447
- ```swift
448
- struct LoginPage {
381
+ @MainActor
382
+ struct SignInPage {
449
383
  let app: XCUIApplication
450
- var emailField: XCUIElement { app.textFields["Email"] }
451
- var passwordField: XCUIElement { app.secureTextFields["Password"] }
452
- var signInButton: XCUIElement { app.buttons["Sign In"] }
384
+ var emailField: XCUIElement { app.textFields["signIn.email"] }
385
+ var passwordField: XCUIElement { app.secureTextFields["signIn.password"] }
386
+ var guestButton: XCUIElement { app.buttons["signIn.guest"] }
453
387
 
454
388
  @discardableResult
455
- func login(email: String, password: String) -> HomePage {
456
- emailField.tap(); emailField.typeText(email)
457
- passwordField.tap(); passwordField.typeText(password)
458
- signInButton.tap()
459
- return HomePage(app: app)
389
+ func continueAsGuest() -> CatalogPage {
390
+ guestButton.tap()
391
+ return CatalogPage(app: app)
460
392
  }
461
393
  }
462
-
463
- struct HomePage {
464
- let app: XCUIApplication
465
- var welcomeLabel: XCUIElement { app.staticTexts["Welcome"] }
466
- }
467
394
  ```
468
395
 
469
- ## Performance Testing
396
+ Page objects wrap element lookups by accessibility identifier and expose
397
+ actions that return the next page. `XCUIApplication` and `XCUIElement` are
398
+ main-actor types, so under Swift 6 the test class and every page object are
399
+ `@MainActor`, and setup goes in the async `setUp()`: the synchronous
400
+ `setUpWithError()` override stays nonisolated and cannot touch them.
401
+
402
+ ## Performance Tests Stay on XCTest
470
403
 
471
404
  ```swift
472
- class FeedPerformanceTests: XCTestCase {
473
- func testFeedParsingPerformance() throws {
474
- let data = try loadFixture("large-feed.json")
475
- let metrics: [XCTMetric] = [XCTClockMetric(), XCTMemoryMetric()]
476
- measure(metrics: metrics) {
477
- _ = try? FeedParser.parse(data)
405
+ final class ParserPerformanceTests: XCTestCase {
406
+ func testParseLargeFeed() {
407
+ measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) {
408
+ _ = FeedParser().parse(Fixtures.largeFeed)
478
409
  }
479
410
  }
480
411
  }
481
412
  ```
482
413
 
483
- Performance tests require XCTest - not available in Swift Testing.
414
+ ## Snapshot Tests
484
415
 
485
- ## Snapshot Testing
486
-
487
- Add Point-Free's `SnapshotTesting` package to the test target via Swift Package Manager, then use it for visual regression. Requires XCTest:
416
+ The open-source swift-snapshot-testing package, added through SwiftPM to the
417
+ test target, is the common choice. It started as an XCTest tool; releases from
418
+ 1.17 on also work inside Swift Testing.
488
419
 
489
420
  ```swift
490
421
  import SnapshotTesting
422
+ import SwiftUI
491
423
  import XCTest
492
424
 
493
- class ProfileViewSnapshotTests: XCTestCase {
494
- func testProfileView() {
495
- let view = ProfileView(user: .preview)
496
- assertSnapshot(of: view, as: .image(layout: .device(config: .iPhone13)))
497
-
498
- // Dark mode
499
- assertSnapshot(of: view.environment(\.colorScheme, .dark),
500
- as: .image(layout: .device(config: .iPhone13)), named: "dark")
425
+ final class ReceiptViewSnapshotTests: XCTestCase {
426
+ func testReceipt() {
427
+ let receipt = ReceiptView(receipt: .sample)
428
+ let phone = SwiftUISnapshotLayout.device(config: .iPhone13)
501
429
 
502
- // Large Dynamic Type
503
- assertSnapshot(of: view.environment(\.dynamicTypeSize, .accessibility3),
504
- as: .image(layout: .device(config: .iPhone13)), named: "largeText")
430
+ assertSnapshot(of: receipt, as: .image(layout: phone))
431
+ assertSnapshot(of: receipt.environment(\.colorScheme, .dark),
432
+ as: .image(layout: phone), named: "night")
433
+ assertSnapshot(of: receipt.dynamicTypeSize(.accessibility3),
434
+ as: .image(layout: phone), named: "largest-text")
505
435
  }
506
436
  }
507
437
  ```
508
438
 
509
- Always test Dark Mode and large Dynamic Type in snapshots.
510
-
511
- ## Test Attachments
439
+ Always include a Dark Mode snapshot and one at a large Dynamic Type size.
512
440
 
513
- Attach diagnostic data to test results for debugging failures:
441
+ ## Attachments from Files
514
442
 
515
443
  ```swift
516
- @Test func generateReport() async throws {
517
- let report = try generateReport()
518
- // Attach the output for later inspection
519
- Attachment.record(report.data, named: "report.json")
520
- #expect(report.isValid)
521
- }
522
-
523
- // Attach from a file URL
524
- @Test func processImage() async throws {
525
- let output = try processImage()
526
- let attachment = try await Attachment(contentsOf: output.url, named: "result.png")
527
- Attachment.record(attachment)
444
+ @Test func archivesLog() async throws {
445
+ let url = try LogExporter().export()
446
+ let logFile = try await Attachment(contentsOf: url, named: "session.log")
447
+ Attachment.record(logFile)
528
448
  }
529
449
  ```
530
450
 
531
- Attachments support standard `Attachable` values such as `Data`, `[UInt8]`, strings, and Encodable values when Foundation is imported. Image attachments require Swift 6.3 / Xcode 26.4 or newer and support platform image types such as `UIImage`, `CGImage`, `CIImage`, and `NSImage`; pass `as: .png` or another supported format when the filename should be explicit.
532
-
533
- ## Exit Testing
451
+ `Data`, `[UInt8]` and `String` can be attached directly. With Foundation
452
+ imported, an `Encodable` type (or an `NSSecureCoding` one) gets a default
453
+ implementation once it declares `Attachable` conformance.
534
454
 
535
- Test code that calls `exit()`, `fatalError()`, or `preconditionFailure()`. Exit testing requires Swift 6.2 / Xcode 26.0 or newer and is supported on macOS, Linux, FreeBSD, OpenBSD, and Windows runtime targets, not iOS, tvOS, or watchOS:
536
-
537
- ```swift
538
- @Test func invalidInputCausesExit() async {
539
- await #expect(processExitsWith: .failure) {
540
- processInvalidInput() // calls fatalError()
541
- }
542
- }
543
- ```
455
+ ## Exit Tests
544
456
 
545
- Exit testing runs the closure in a subprocess. The test passes if the process exits with the expected status. Capturing values from the parent process in an exit-test capture list requires the Swift 6.3 compiler.
457
+ An exit test runs its closure in a child process and passes when that process
458
+ ends with the expected status. See the skill for the toolchain and platform
459
+ limits.
546
460
 
547
- ## Test File Organization
461
+ ## Organising Files
548
462
 
549
463
  ```text
550
- Tests/AppTests/ # Swift Testing (Models/, ViewModels/, Services/)
551
- Tests/AppUITests/ # XCTest UI tests (Pages/, Flows/)
552
- Tests/Fixtures/ # Test data (JSON, images)
553
- Tests/Mocks/ # Shared mock implementations
464
+ Tests/
465
+ AppTests/ Swift Testing
466
+ Models/
467
+ ViewModels/
468
+ Services/
469
+ AppUITests/ XCTest UI tests
470
+ Pages/
471
+ Flows/
472
+ Fixtures/
473
+ Mocks/
554
474
  ```
555
475
 
556
- Name test files `<TypeUnderTest>Tests.swift`. Describe behavior in function names: `fetchUserReturnsNilOnNetworkError()` not `testFetchUser()`. Name mocks `Mock<ProtocolName>`.
476
+ - Files are named after the type under test: `InvoiceStoreTests.swift`.
477
+ - Test functions describe behaviour.
478
+ - Doubles are named after the protocol: `MockProfileStore` for
479
+ `ProfileStore` (or `Fake...` when it has working behaviour).
557
480
 
558
- ### What to Test
481
+ Always test business rules, validation, view-model state transitions, error
482
+ paths, edge cases (empty input, nil, boundary values), both outcomes of async
483
+ calls, and cancellation of `Task`s.
559
484
 
560
- **Always test:** business logic, validation rules, state transitions in view models, error handling paths, edge cases (empty collections, nil, boundaries), async success and failure, Task cancellation.
485
+ Skip SwiftUI `body` layout (snapshots cover it), trivial forwarding code, the
486
+ behaviour of Apple frameworks, and private methods (test them through the
487
+ public API).
561
488
 
562
- **Skip:** SwiftUI view body layout (use snapshots), simple property forwarding, Apple framework behavior, private methods (test through public API).
489
+ ## Readable Arguments
563
490
 
564
- ## CustomTestStringConvertible
565
-
566
- When parameterized test arguments appear in test output, Swift Testing uses `String(describing:)` by default. Conform to `CustomTestStringConvertible` for better output:
491
+ Parameterized results print each argument with `String(describing:)`. For
492
+ enums, identifiers and models, conform to `CustomTestStringConvertible`:
567
493
 
568
494
  ```swift
569
- enum Food: CaseIterable {
570
- case paella, oden, ragu
571
- }
572
-
573
- extension Food: CustomTestStringConvertible {
574
- var testDescription: String {
575
- switch self {
576
- case .paella: "paella valenciana"
577
- case .oden: "おでん"
578
- case .ragu: "ragù alla bolognese"
579
- }
580
- }
495
+ extension Plan: CustomTestStringConvertible {
496
+ var testDescription: String { "plan:\(rawValue)" }
581
497
  }
582
-
583
- @Test(arguments: Food.allCases)
584
- func isDelicious(_ food: Food) { /* output shows custom descriptions */ }
585
498
  ```
586
499
 
587
- Use this for any type passed as a parameterized test argument where the default description is unclear - especially enums, IDs, or model types.
588
-
589
- ## Availability-Conditional Tests
590
-
591
- Use `@available` on test functions to run tests only on specific OS versions:
500
+ ## Tests That Need a Newer OS
592
501
 
593
502
  ```swift
594
- @Test
595
- @available(iOS 18, macOS 15, *)
596
- func usesNewAPI() async throws {
597
- let result = try await NewFramework.process()
598
- #expect(result.isValid)
599
- }
503
+ @Test @available(macOS 15, iOS 18, *)
504
+ func usesNewMeshGradientAPI() { }
600
505
  ```
601
506
 
602
- Swift Testing skips `@available`-gated tests when running on older OS versions. This replaces XCTest's `#available` guard + early return pattern.
603
-
604
- Do not put `@available` on a suite type or a type that contains a suite; Swift Testing requires suite types to always be available. Put availability gates on individual `@Test` functions instead.
605
-
606
- ## Common Mistakes and Review Checklist
607
-
608
- 1. **Testing implementation, not behavior.** Test what the code does, not how.
609
- 2. **No error path tests.** If a function can throw, test the throw path.
610
- 3. **Flaky async tests.** Use `confirmation` with expected counts, not `sleep` calls.
611
- 4. **Shared mutable state between tests.** Each test sets up its own state via `init()` in `@Suite` or a fixture.
612
- 5. **Missing accessibility identifiers in UI tests.** XCUITest queries rely on them.
613
- 6. **Using `sleep` in tests.** Use `confirmation`, clock injection, or `withKnownIssue`.
614
- 7. **Not testing cancellation.** If code supports `Task` cancellation, verify it cancels cleanly.
615
- 8. **Unclear XCTest migration boundaries.** Apple allows XCTest and Swift Testing in one file during migration; prefer separate files when it keeps imports, ownership, and runner expectations clearer.
616
- 9. **Non-Sendable test helpers shared across tests.** Ensure test helper types are Sendable when shared across concurrent test cases.
617
- 10. **Assuming test order.** Parallel default execution means declaration order and suite nesting do not create a workflow.
618
- 11. **Using `.serialized` as a dependency chain.** Serialized scopes avoid overlap; they do not pass state from one test to the next.
619
-
620
- ### Review Checklist
621
-
622
- - [ ] All new tests use Swift Testing (`@Test`, `#expect`), not XCTest assertions
623
- - [ ] Test names describe behavior (`fetchUserReturnsNilOnNetworkError` not `testFetchUser`)
624
- - [ ] Error paths have dedicated tests
625
- - [ ] Async tests use `confirmation()`, not `Task.sleep`
626
- - [ ] Parameterized tests used for repetitive variations
627
- - [ ] Tags applied for filtering (`.critical`, `.slow`)
628
- - [ ] Mocks conform to protocols, not subclass concrete types
629
- - [ ] No shared mutable state between tests
630
- - [ ] Tests do not rely on declaration order or shared suite instances
631
- - [ ] `.serialized` is reserved for exclusive state, not workflow sequencing
632
- - [ ] Cancellation tested for cancellable async operations
507
+ Swift Testing skips the test on older systems, which replaces XCTest's
508
+ `#available` check with an early return. Suites must always be available:
509
+ never put `@available` on a suite or on the type that contains it.
510
+
511
+ ## More Mistakes to Avoid
512
+
513
+ - Neither declaration order nor nesting of suites turns tests into a workflow;
514
+ they still run in parallel.
515
+ - A serialized suite prevents overlap, but tests in it still do not hand state
516
+ to each other.